추상 메서드와 클래스

추상 클래스는 abstract으로 선언된 클래스입니다 —추상 메서드를 포함할 수도 있고 포함하지 않을 수도 있습니다. 추상 클래스는 인스턴스화할 수 없지만 서브클래스는 만들 수 있습니다.

추상 메서드는 다음과 같이 구현 없이(중괄호 없이 세미콜론 뒤에) 선언되는 메서드입니다:

abstract void moveTo(double deltaX, double deltaY);

클래스에 추상 메서드가 포함된 경우 클래스 자체는 다음과 같이 abstract으로 선언해야 합니다:

public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

추상 클래스가 서브 클래스화되면, 서브 클래스는 일반적으로 부모 클래스의 모든 추상 메서드에 대한 구현을 제공합니다. 그러나 그렇지 않은 경우 서브클래스도 abstract으로 선언해야 합니다.

Note: 인터페이스(인터페이스 섹션 참조)에서 기본 또는 정적으로 선언되지 않은 메서드는 암시적으로 추상적이므로 추상 수정자는 인터페이스 메서드와 함께 사용되지 않습니다. (사용할 수는 있지만 불필요합니다.)

 

인터페이스와 비교한 추상 클래스

추상 클래스는 인터페이스와 유사합니다. 인스턴스화할 수 없으며, 구현을 포함하거나 포함하지 않고 선언된 메서드가 혼합되어 있을 수 있습니다.
그러나 추상 클래스를 사용하면 non-static, non-final 필드를 선언할 수 있고, public, protected, private의 구체적인 메서드를 정의할 수 있습니다.
인터페이스를 사용하면 모든 필드가 자동으로 public, protected, private이 되며, 사용자가 선언하거나 정의하는 모든 메서드는 (기본 메서드로서) public이 됩니다. 또한 클래스는 추상적이든 아니든 하나의 클래스만 확장할 수 있습니다. 반면, 인터페이스는 얼마든지 구현할 수 있습니다.

추상 클래스 또는 인터페이스 중 어떤 것을 사용해야 하나요?

  • 이러한 상황 중 하나라도 해당되는 경우 추상 클래스 사용을 고려하세요:

    • 밀접하게 관련된 여러 클래스 간에 코드를 공유하려는 경우.
    • 추상 클래스를 확장하는 클래스에 공통 메서드나 필드가 많거나 public 이외의 접근 수정자(예: protectedprivate)가 필요할 것으로 예상되는 경우.
    • non-static, non-final 필드를 선언하려는 경우. 이를 통해 메서드가 속한 객체의 상태에 액세스하고 수정할 수 있는 메서드를 정의할 수 있습니다.
  • 이러한 상황 중 하나라도 상황에 해당되는 경우 인터페이스 사용을 고려하세요:

    • 관련 없는 클래스가 인터페이스를 구현할 것으로 예상합니다. 예를 들어, ComparableCloneable 인터페이스는 관련 없는 많은 클래스에 의해 구현됩니다.
    • 특정 데이터 타입의 동작을 지정하고 싶지만 누가 그 동작을 구현하는지는 신경 쓰지 않으려는 경우.
    • 타입의 다중 상속을 활용하려는 경우.

JDK의 추상 클래스의 예로는 컬렉션 프레임워크의 일부인 AbstractMap이 있습니다. 그 하위 클래스인 HashMap, TreeMap, ConcurrentHashMap은 (get(), put(), put(), isEmpty(), containsKey(), containsValue()등 포함) AbstractMap이 정의하는 많은 메서드들을 공유합니다.

JDK에서 여러 인터페이스를 구현하는 클래스의 예로는 HashMap이 있으며, Serializable, Cloneable, Map<K, V> 인터페이스를 구현합니다. 이 인터페이스 목록을 읽으면 (클래스를 구현한 개발자나 회사에 관계없이) HashMap의 인스턴스를 복제할 수 있고, 직렬화 가능하며(즉, 바이트 스트림으로 변환할 수 있음을 의미함; 직렬화 가능한 객체 섹션 참조), 지도의 기능을 가지고 있다는 것을 유추할 수 있습니다. 또한, Map<K, V> 인터페이스는 이 인터페이스를 구현한 이전 클래스가 정의할 필요가 없는 merge()forEach() 등 많은 기본 메서드로 향상되었습니다.

많은 소프트웨어 라이브러리는 추상 클래스와 인터페이스를 모두 사용하며, HashMap 클래스는 여러 인터페이스를 구현하고 추상 클래스인 AbstractMap도 확장합니다.

 

추상 클래스 예제

객체 지향 그리기 애플리케이션에서는 원, 직사각형, 선, 베지어 곡선 및 기타 여러 그래픽 객체를 그릴 수 있습니다. 이러한 객체에는 모두 특정 상태(예: 위치, 방향, 선 색상, 채우기 색상)와 동작(예: moveTo, 회전, 크기 조정, 그리기)이 공통적으로 있습니다. 이러한 상태와 동작 중 일부는 모든 그래픽 객체에 대해 동일합니다(예: 위치, 채우기 색상, moveTo). 다른 것들은 다른 구현이 필요합니다(예: 크기 조정 또는 그리기).

모든 그래픽 객체는 스스로 그리거나 크기를 조정할 수 있어야 하며, 그 방법만 다를 뿐입니다. 이는 추상 슈퍼클래스를 위한 완벽한 상황입니다. 유사점을 활용하여 모든 그래픽 객체가 동일한 추상 부모 객체에서 상속하도록 선언할 수 있습니다(예: GraphicObject).

먼저, 현재 위치와 moveTo() 메서드와 같이 모든 서브클래스가 전적으로 공유하는 멤버 변수와 메서드를 제공하기 위해 GraphicObject라는 추상 클래스를 선언합니다. GraphicObject는 또한 모든 서브클래스가 구현해야 하지만 다른 방식으로 구현해야 하는 draw() 또는 resize()와 같은 메서드에 대한 추상 메서드를 선언합니다. GraphicObject 클래스는 다음과 같이 보일 수 있습니다:

abstract class GraphicObject {
    int x, y;
    ...
    void moveTo(int newX, int newY) {
        ...
    }
    abstract void draw();
    abstract void resize();
}

직사각형과 같은 GraphicObject의 각 비추상 하위 클래스는 draw()resize() 메서드에 대한 구현을 제공해야 합니다:

class Circle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}
class Rectangle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}

 

추상 클래스가 인터페이스를 구현하는 경우

인터페이스 섹션에서 인터페이스를 구현하는 클래스는 인터페이스의 모든 메서드를 구현해야 한다고 언급했습니다. 그러나 클래스가 abstract으로 선언되어 있다면 인터페이스의 모든 메서드를 구현하지 않는 클래스를 정의할 수 있습니다. 예를 들어

abstract class X implements Y {
  // implements all but one method of Y
}
 
class XX extends X {
  // implements the remaining method in Y
}

이 경우 클래스 XY를 완전히 구현하지 않으므로 추상적이어야 하지만, 클래스 XX는 실제로 Y를 구현합니다.

 

클래스 멤버

추상 클래스에는 static 필드와 static 메서드가 있을 수 있습니다. 이러한 static 멤버는 다른 클래스에서와 마찬가지로 클래스 참조(예: AbstractClass.staticMethod())와 함께 사용할 수 있습니다.