1. 추상 클래스란?

  • 추상 클래스(Abstract Class)는 공통된 필드와 메서드를 자식 클래스에서 상속받아 사용할 수 있도록 하는 클래스입니다.
  • 추상 클래스는 실체가 없는 클래스로, 인스턴스를 직접 생성할 수 없습니다.
  • 추상 클래스는 일반 메서드와 추상 메서드를 모두 포함할 수 있으며, 자식 클래스에서 추상 메서드를 반드시 구현해야 합니다.

추상 클래스의 특징

  • new 연산자로 인스턴스를 생성할 수 없습니다. 추상 클래스는 직접적으로 사용할 수 없으며, 자식 클래스에서 상속받아 사용해야 합니다.
  • 공통적인 필드나 메서드를 정의하여, 이를 자식 클래스들이 상속받아 재사용할 수 있습니다.
  • 추상 메서드는 선언만 되어 있고, 구체적인 구현은 자식 클래스에서 이루어져야 합니다.


2. 추상 클래스와 추상 메서드 구현

추상 클래스 Shape와 이를 상속받는 CircleSquare 클래스입니다.

// 추상 클래스
public abstract class Shape {
    String color;

    // 추상 클래스의 생성자
    public Shape(String color) {
        this.color = color;
    }

    // 추상 메서드
    abstract void draw();

    // 일반 메서드
    void start() {
        System.out.println("도형을 그려보자~");
    }
}
  • 추상 클래스 Shape는 공통 필드인 color를 정의하고, 도형을 그리는 동작을 정의하는 추상 메서드 draw()를 선언했습니다. 또한, 모든 도형에서 사용할 수 있는 일반 메서드 start()도 포함하고 있습니다.
  • 추상 메서드인 draw()는 선언만 되어 있고, 구체적인 구현은 자식 클래스에서 이루어집니다.


3. 자식 클래스에서 추상 메서드 구현하기

추상 클래스 Shape를 상속받은 CircleSquare는 각각의 도형을 그리는 방식으로 draw() 메서드를 재정의(오버라이딩)해야 합니다.

// 원(Circle) 클래스
public class Circle extends Shape {
    public Circle(String color) {
        super(color);
    }

    // 추상 메서드 구현
    @Override
    void draw() {
        System.out.println("원 그리기!");
    }
}
// 사각형(Square) 클래스
public class Square extends Shape {
    public Square(String color) {
        super(color);
    }

    // 추상 메서드 구현
    @Override
    void draw() {
        System.out.println("사각형 그리기!");
    }
}
  • Circle 클래스와 Square 클래스는는 부모 클래스인 Shape의 추상 메서드 draw()를 오버라이딩하여 “원 그리기!”와 “사각형 그리기!”를 출력합니다.


4. 추상 클래스 활용 예

Shape 클래스를 상속받은 CircleSquare 객체를 생성하여 추상 클래스와 메서드를 활용하는 방법입니다.

public class ShapeEx {
    public static void main(String[] args) {
        // 추상클래스는 직접 인스턴스 생성 불가능
        // Shape shape = new Shape("green");  // 컴파일 에러

        // 자식 클래스의 인스턴스 생성
        Circle circle = new Circle("blue");
        Square square = new Square("yellow");

        // 매개변수 다형성: Shape 타입으로 각각의 자식 클래스 참조 가능
        go(circle);
        go(square);
    }

    public static void go(Shape shape) {
        shape.start();  // 부모 클래스의 일반 메서드 호출
        shape.draw();   // 자식 클래스에서 구현된 추상 메서드 호출
        System.out.println("도형의 색상은 " + shape.color);
    }
}

1) 설명

  • 추상 클래스 Shape는 직접 인스턴스를 생성할 수 없기 때문에 Shape shape = new Shape("green");과 같은 코드로는 객체를 만들 수 없습니다.
  • 대신, 자식 클래스인 CircleSquare 객체를 생성하여 다형성을 통해 Shape 타입으로 참조할 수 있습니다.
  • go() 메서드는 Shape 타입의 매개변수를 받지만, 실제로는 Circle 또는 Square 객체를 전달받아 자식 클래스에서 구현된 draw() 메서드를 호출합니다.

2) 실행 결과

도형을 그려보자~
원 그리기!
도형의 색상은 blue
도형을 그려보자~
사각형 그리기!
도형의 색상은 yellow

이처럼 추상 클래스와 추상 메서드를 활용하면, 공통된 동작을 부모 클래스에서 정의하고, 구체적인 동작은 자식 클래스에서 구현함으로써 유연하고 확장 가능한 구조를 만들 수 있습니다.


5. 추상 클래스를 사용하는 이유

1) 공통된 기능의 코드 재사용

추상 클래스는 공통된 필드와 메서드를 상속받아 자식 클래스에서 재사용할 수 있도록 도와줍니다. 이를 통해 중복된 코드를 최소화할 수 있습니다. 모든 자식 클래스에서 동일하게 사용될 수 있는 부분은 추상 클래스에서 한 번만 정의하면 되므로, 코드의 유지보수성이 높아집니다.

2) 구현 강제성 제공

추상 클래스는 추상 메서드를 선언함으로써 자식 클래스에서 해당 메서드를 반드시 구현하도록 강제합니다. 자식 클래스가 부모 클래스의 추상 메서드를 오버라이딩하지 않으면 컴파일 오류가 발생하므로, 자식 클래스들이 특정한 기능을 반드시 구현해야 할 때 유용합니다.

3) 다형성 제공

추상 클래스는 다형성(polymorphism)을 활용할 수 있는 구조를 제공합니다. 즉, 부모 클래스 타입으로 여러 자식 클래스를 처리할 수 있습니다. 이를 통해 유연한 코드 설계가 가능하며, 하나의 인터페이스(부모 클래스)를 통해 다양한 자식 클래스들을 처리할 수 있어 확장성이 높아집니다.

예를 들어, Shape 클래스는 여러 자식 클래스를 하나의 타입(Shape)으로 처리할 수 있어, 각각의 자식 클래스가 어떤 구체적인 구현을 가졌는지 신경 쓰지 않고 통일된 방식으로 작업을 진행할 수 있습니다.

4) 추상 클래스는 객체 생성 방지

추상 클래스는 직접적으로 인스턴스를 생성할 수 없습니다. 이는 해당 클래스가 실체를 가지지 않는 공통적인 특성만을 정의하고, 그 구체적인 구현은 자식 클래스에게 위임하기 때문에, 인스턴스를 생성하지 못하도록 설계된 것입니다. 이를 통해 객체 지향 프로그래밍에서 구현과 인터페이스를 분리하는 중요한 역할을 합니다.


예시 코드에서는?

  • Shape 추상 클래스는 도형이라는 공통된 특성을 제공하며, color 필드와 start() 메서드처럼 공통적으로 사용될 부분을 정의합니다. 이를 통해 공통 기능을 재사용하고, 각 자식 클래스는 이를 상속받아 사용할 수 있습니다.

  • draw() 추상 메서드는 모든 도형이 반드시 그려져야 하지만, 구체적인 그리는 방식은 도형마다 다르므로, 자식 클래스가 반드시 각자의 방식으로 구현하도록 강제하고 있습니다. CircleSquare는 각기 다른 방식으로 이 메서드를 구현하고, 이를 통해 다형성을 제공합니다.