추상클래스와 인터페이스
자바에서 다형성을 효과적으로 보장해주는 개념이 추상클래스와 인터페이스 입니다.
각각이 지닌 특징이 무엇이고, 두 개념의 차이는 어떠한지에 대해 자세히 살펴보도록 하겠습니다.
추상클래스
추상클래스는 하나 이상의 추상 메서드를 가지는 클래스를 뜻합니다.
여기서 추상 메서드는 선언부만 작성하고 구현부는 작성하지 않은 상태로 남겨둔 메서드를 뜻합니다.
따라서 상속받은 클래스에 따라 구현 내용이 달라질 수도 있는 특징이 있습니다.
public abstract class 클래스이름 {
public abstract void 메서드이름();
}
예시는 위의 코드와 같습니다. 접근 제어자 뒤에 abstract를 붙여주면 추상클래스 또는 추상 메서드가 됩니다.
여기서 주의할 점이
추상 메서드를 가진 클래스는 반드시 추상 클래스여야 하지만, 추상 클래스는 반드시 추상 메서드를 가질 필요는 없다는 것입니다.
위의 그림을 보면 추상 클래스 추상 메서드를 지닌 클래스가 abstract로 설정되어 있지 않을 경우, 빨간색 밑줄로
반드시 해당 클래스가 abstract로 선언되어야 하는 오류 메세지가 나오는 것을 알 수 있습니다.
반면에 추상 클래스는 추상 메서드를 가지지 않아도 오류 메세지가 안나오는 것을 확인 할 수 있습니다.
또한 추상 클래스는 추상 메서드를 가질 수 있다는 점, 그리고 추상 클래스를 통해 객체를 생성할 수 없다는 점 외에는
클래스와 동일한 기능을 수행할 수 있습니다.
이 지점에서 인터페이스와 차이를 보이는데, 추상 클래스는 변수의 사용에 있어서 제한이 없습니다. 반면 인터페이스는 상수 형태의 변수만 사용이 가능합니다.
추상 클래스는 보시는 바와 같이 변수의 접근제어자로 public, default, protected, private 모두 사용이 가능하고
인스턴스 변수와 static으로 선언된 클래스 변수도 사용할 수 있습니다.
이를 정리하자면, 추상 클래스는 다음과 같은 특징을 지닙니다.
1. 사용 가능 변수에 제한이 없다.
2. 사용 가능 접근 제어자에 제한이 없다.
3. 사용 가능 메서드에 제한이 없다. (추상 메서드가 아닌 일반 메서드도 선언이 가능하다)
첫 번째 특징인 사용 가능 변수에 제한이 없다는 것은 중복 멤버의 통합을 가능케 해줍니다.
예시를 들어 설명해 보겠습니다.
다음과 같이 AbstractExample 추상 클래스는 사람의 키를 나타내는 height와 gender을 속성으로 지닙니다.
PeopleA는 이러한 속성을 따로 선언하지 않더라도 사용할 수 있습니다. 하지만 이는 일반적인 클래스 상속과
크게 다르지 않습니다. 그렇다면 왜 추상 클래스를 사용할까요?
위 그림은 추상 클래스가 아닌 일반 클래스를 사용한 예시입니다. CommonClass에서 작성한 printGreetingMessage
메서드를 People는 OverRide를 사용하지 않고 쓸 수 있습니다.
하지만 개발자가 CommonClass의 printGreetingMessage 메서드를 만든 의도는
자신을 상속한 모든 클래스가 해당 메서드를 오버라이드하여 사용하기를 의도하였다고 가정해봅시다.
특정 개발자는 해당 메서드를 오버라이드 해서 쓰기도 하겠지만, 해당 내용을 잘 모르는 개발자의 경우 기존에
작성된 메서드를 그대로 쓸 수도 있을 것입니다.
일반적인 클래스로는 부모 클래스의 메서드를 그대로 사용하거나 오버라이드 하는 것을 선택사항입니다.
따라서 오버라이드를 강제할 수 없습니다.
하지만 추상 클래스는 선언된 추상 메서드를 자식 클래스에서 오버라이드를 하지 않으면 에러를 발생시킵니다.
정리하자면, 추상클래스를 활용하여 선언한 메서드를 자식 클래스가 오버라이드 하도록 강제 할 수 있고
추상클래스에서 선언한 필드를 자식 클래스에서도 사용할 수 있는 것입니다.
클래스와 추상클래스의 또 다른 차이로는 일반 클래스는 그 자체로 객체 생성이 가능한 반면, 추상 클래스는
그 자체로 객체 생성이 불가능합니다.
위의 그림을 보면, CommonClass의 경우 객체 생성이 그 자체로 가능합니다. 하지만 추상 클래스에 해당하는
AbstractExample의 경우 인스턴스화 시킬 수 없다는 메세지가 빨간줄로 표시된 것을 알 수 있습니다.
이처럼 추상클래스는 클래스가 지니는 특징을 그대로 사용가능하되, 추상 메서드를 사용할 수 있고 그 자체로
객체를 생성하지 못한다는 점에서 일반적인 클래스와 차이를 보입니다.
인터페이스
인터페이스는 어떻게 보면 추상 클래스와 동일한 기능을 수행한다고 볼 수 있습니다. 추상 메서드를 활용하여
자신을 구현한 클래스에게 해당 메서드의 구체적인 구현을 강제하는 면에서 공통점이 있기 때문입니다.
InterfaceExample과 같은 인터페이스가 있다고 가정해봅시다. 해당 인터페이스 안에는 methodA라는 추상 메서드가
선언되어 있습니다.
추상클래스에서는 extends를 통해 상속을 받았지만, 인터페이스 같은 경우는 implements를 통해 구현한다고 합니다.
위의 ClassA라는 클래스는 InterfaceExample을 implements를 통해 구현했습니다. 또한 인터페이스에 선언된
추상 메서드를 오버라이드 하여 구현하고 있는 것을 확인할 수 있습니다.
만약 인터페이스를 implements 하고도 인터페이스 안에 선언된 추상 메서드를 구현하지 않으면 밑에와 같이 빨간색
밑줄로 에러 메세지가 나타납니다.
이렇게 보면 인터페이스와 추상클래스는 얼핏 동일한 역할을 수행하는것 같아 보입니다. 그렇다면 둘은 서로
어떤 차이가 있을까요?
1. 사용 가능 변수의 차이
앞서 살펴본 추상 클래스의 경우 모든 변수 유형의 사용이 가능했습니다. 하지만 인터페이스는 상수 형태의 변수만
사용이 가능합니다.
다음과 같이 static final로 선언된 TEXT같은 경우는 사용이 가능하지만, height와 같은 인스턴스 변수나 length와 같은
클래스 변수는 사용이 불가능합니다.
그럼 다음과 같은 경우는 뭘까요? final이 따로 안붙어 있는데도 text라는 변수는 사용이 가능합니다.
하지만 해당 인터페이스를 구현한 다른 클래스에서는 final로 선언되지 않은 text가 final로 선언되어 값을 변경할 수 없다고
나오는 것을 확인할 수 있습니다.
이는 인터페이스가 변수를 선언하면 자동으로 public static final 을 앞에 붙여 상수화로 만들어 버리기 때문입니다.
따라서 인터페이스 내에 선언된 변수는, 값을 변경할수도 없는 상수로만 쓰이게 됩니다.
2. 접근 제어자
추상 클래스의 경우 모든 접근제어자의 사용이 가능했습니다. 하지만 인터페이스는 public 접근 제어자만 사용 가능합니다.
위의 예시를 보면, 각각 다른 접근제어자로 지정된 method들을 확인할 수 있습니다. 인터페이스는 public을 제외한
모든 접근제어자의 사용을 하지 못하게 되어있습니다. protected와 private 접근제어자는 빨간색 밑줄로 에러 표시가
뜨지만, 아무 접근제어자도 설정되어 있지 않은(default 접근제어자) methodC같은 경우는 에러 표시가 뜨지 않습니다.
인터페이스가 public 뿐만 아니라 default 접근제어자도 허용하는 것인가요?
classA는 InterfaceExample과 다른 패키지에 선언된 클래스입니다. 따라서 같은 패키지에서만 접근가능한 default 접근
제어자로 선언된 변수 b를 사용하지 못하는 것이 정상입니다.
하지만 보시는 바와 같이 pulbic으로 선언된 a와 default로 선언된 b 모두 사용하는 것을 볼 수 있습니다.
InterfaceExample 인터페이스 내에 선언된 b라는 변수는 default 접근제어자를 사용하는 것 처럼 보여도, 인터페이스가
자체적으로 public 접근제어자를 붙여주었기 때문에 사용이 가능한 것입니다.
다시 인터페이스 내부의 변수를 살펴보면 public이라는 접근제어자가 redundant(불필요하다)한 상태인 것을 확인할 수
있습니다. static도 마찬가지고 final도 마찬가지입니다.
즉 다음 처럼 public static final과 같은 키워드를 붙여주지 않아도 인터페이스는 변수로 선언된 것들 앞에 해당 키워드를
자동으로 붙여주는 것을 알 수 있습니다.
3. 사용 가능 메서드
인터페이스는 원래 추상 메서드만 사용 가능했습니다. public abstract을 메서드 앞에 꼭 붙여줘야 했던 것이죠.
하지만 JAVA 8 이후로 다양한 메서드의 사용이 가능하도록 변경되었습니다.
위의 예시를 보시면, public, default, private, static 메서드들을 사용할 수 있는 것을 알 수 있습니다.
protected 접근 제어자를 지닌 메서드만 제외하고 모두 쓸 수 있는 것이죠.
<수정>
여기서 쓰는 default 메서드는 접근제어자를 뜻하는 것이 아닌, 인터페이스에서 구현이 되어 있는 메서드를 뜻하는
것입니다. 접근 제어자와는 아무런 상관이 없다고 합니다.
또한 private 메서드는 static 메서드 또는 default 메서드 내에서 쓰일 수 있도록 JAVA9 이후로 나온 형태라고
합니다.
private 메서드가 있기 전에는, default 메서드 또는 static 메서드 내에 쓰이는 메서드들을 public으로 쓸 수 밖에
없었기 때문에 해당 메서드의 접근을 막기 위해 나온 형태라고 합니다.
(protected 접근제어자를 지닌 메서드만 쓸 수 없게 만든 이유는 잘 모르겠습니다..)
Protectd 접근 제어자를 지닌 methodE를 삭제하고 private 접근제어자를 지닌 methodC의 테스트를 위해
methodB 내부에서 methodC를 호출하는 방식으로 인터페이스 내부 로직을 수정하였습니다.
다음과 같이 InterfaceExample을 구현하고 있는 classA 클래스는 methodA만 오버라이드 하여 구현하면 이미 구현된
다른 메서드들은 추가적으로 구현할 필요가 없습니다.
InterfaceExample 내부에 선언된 static 메서드 methodD는 인터페이스 레벨에서 사용이 가능합니다. 이를 제외한
다른 모든 메서드는 해당 인터페이스를 구현한 클래스의 객체를 통해서만 사용이 가능합니다.
해당 결과를 출력하면
보시는 바와 같이 static 메서드와 추상 메서드, private 메서드, default 메서드 모두 정상적으로
실행되는 것을 확인할 수 있습니다.
변수에서 접근제어자를 사용할 때 public을 따로 안붙여줘도 인터페이스 내부에서 자동으로 붙여준 것을
확인할 수 있었습니다. 이와 마찬가지로 메서드 또한 public을 안붙여줘도 자동으로 public abstract를
앞에다 붙여줍니다.
public과 abstract를 자동으로 붙여주니 불필요하다 라는 메세지가 출력 되고 있습니다.
따라서 위와 같이 메서드를 선언하면, public으로 선언된 추상 메서드를 선언한 것과 같은 효과를 같게 됩니다.
defaut 메서드 처럼 보여도 public abstract가 붙은 메서드인 것이죠
정리
추상 클래스와 인터페이스의 공통점과 차이점에 대해 정리해보겠습니다.
차이점은 밑에 표와 같습니다.
두 개념의 공통점은 다음과 같습니다.
1. 자체로 인스턴스화 (객체 생성) 을 할 수 없다.
2. 선언된 추상메서드는 구현 또는 상속 받은 클래스에서 반드시 구현해야 한다.
활용 방안
그렇다면 어떨 때 추상클래스를 사용하고, 어떨 때 인터페이스를 사용해야 할까요?
보통 이를 구분하기 위해 두가지 키워드를 사용합니다
1. Is a kind of (~의 한 종류) - 추상 클래스에 적합
2. be able to (~ 할 수 있는) - 인터페이스에 적합
이를 이해하기 쉽게 동물을 예시로 들어 설명해보겠습니다.
creature(생물) 이라는 추상 클래스가 있습니다.
해당 추상 클래스는 해당 개체가 어디에서 생활하는지 (육지 or 바다), 어떻게 이동하는지를 추상 메서드로 지니고
있습니다.
그리고 이를 상속받는 추상 클래스가 LandAnimal과 MarinAnimal로나뉩니다.
해당 추상 클래스들은 Creature에서 선언된 livingArea를 각각 구현하고 있습니다. 같은 추상 클래스 이기 때문에
추상 메서드를 구현해도되고, 구현하지 않아도 되기 때문에 move라는 추상 메서드는 구현을 하지 않은 상태입니다.
그리고 이를 상속 받는 일반 클래스인 Human(인간), Bird(새), Fish(물고기)가 있습니다.
인간은 육지동물의 일종입니다. 즉 is a kind of 관계인 것이죠. 따라서 LandAnimal 이라는 추상클래스를 상속받았습니다.
새 또한 육지동물의 일종입니다. 인간과 마찬가지로 LandAnimal을 상속 받았습니다.
반면에 물고기는 바다 동물의 일종입니다.
따라서 MarinAnimal을 상속받았습니다.
각 동물 클래스 안에는 움직이는 방법을 나타내는 메서드가 선언되어 있습니다.
새는 fly(), 인간은 walk(), 물고기는 swim() 이 그 예시입니다.
하지만 동물에 따라 이동하는 방법은 다양할 수가 있습니다. 이를테면, 새는 수영을 하지 못하지만 인간은 수영을 할 수
있습니다.
물고기와 인간은 서로 다른 종이지만 같은 swim 이라는 같은 기능을 공유하고 있습니다.
이러한 공통점을 어떻게 묶을 수 있을까요? 이 때 활용되는 것이 인터페이스입니다.
인터페이스는 is able to의 관계입니다. 인간과 물고기 모두 수영(헤엄)을 할 수 있습니다.
하지만 인간과 물고기가 같은 종이라고 할 수 있을까요?
물론 수영을 할 수 있는 동물과 수영을 하지 못하는 동물의 관점에서 보면 인간과 물고기는 같은 종일 수 있을 것입니다.
하지만 이미 육지동물과 바다동물이라는 abstract 클래스로 묶은 상황에서 swimmingAnimal 이라는 추상 클래스를
사용하여 다시 묶는 것은 불가능합니다.
자바에서 extends 사용 (상속)은 단 한번만 가능하기 때문입니다. 반면 인터페이스 implement(구현) 은 다중 사용이 가능합니다.
따라서 이와 같은 경우 abstract 클래스 보다 인터페이스를 쓰는게 적절합니다.
위의 코드를 수정하면 다음과 같이 바뀝니다.
우선 isAbleToSwim이라는 인터페이스를 생성합니다. 그리고 인간과 물고기가 해당 인터페이스를 구현하도록 implement
합니다.
물론 swim이라는 메서드를 인터페이스 내에서 default 메서드로 선언하여 물고기와 인간 모두 swim() 메서드에서
동일한 출력값을 갖게 할 수도 있지만,
인터페이스의 주 목적이 추상 메서드를 구현하는 것이기도 하는 만큼, swim() 메서드를 추상 메서드로 선언하여
이를 구현하고 있는 클래스에서 해당 메서드를 구현하도록 작성하였습니다.
해당 객체들의 move 메서드와 livingArea 메서드를 메인 메서드에서 출력해보도록 하겠습니다.
결과는 다음과 같습니다.
관계를 그림으로 다시 나타내면 아래와 같습니다.
정리하자면, 추상 클래스의 사용은 해당 클래스와의 관계가 '같은 종'(is a kind of)일 때,
인터페이스의 사용은 해당 클래스와의 관계가 '할 수 있는' (is able to) 일 떄 사용하는 것이 적절합니다.
이상으로 추상클래스와 인터페이스에 대한 글을 마치겠습니다.
긴 글 읽어주셔서 감사합니다!
참고 블로그
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0
[JAVA] ☕ 인터페이스 vs 추상클래스 차이점 - 완벽 이해하기
인터페이스 vs 추상클래스 비교 이 글을 찾아보는 독자분들은 아마도 이미 인터페이스와 추상클래스 개념을 학습한 뒤에 이 둘에 대하여 차이의 모호함 때문에 방문 했겠지만, 그래도 다시한번
inpa.tistory.com
https://da-nyee.github.io/posts/java-abstract-class-vs-interface/
[Java] 추상 메서드 vs 인터페이스 (Abstract Class vs Interface)
Introduction
da-nyee.github.io
'Java' 카테고리의 다른 글
Inner class에 Static을 붙이는 이유 (0) | 2023.04.24 |
---|---|
제너릭이란? (1) | 2023.04.12 |
자바 변수를 메모리 관점에서 뜯어보기 (0) | 2023.04.01 |
가비지 컬렉션에 대해 (0) | 2023.03.21 |
객체지향 프로그래밍이란? (0) | 2023.03.13 |