5. 기초_추상클래스

  • 일반적으로 하나의 추상 메소드를 가지고 있는 클래스를 추상 클래스

  • 추상 메소드는 선언부만 있고 구현부가 없는 메소드(해당 메소드는 서브 클래스에서 반드시 구현)

  • 인스턴스화 할 수 없으며, 일반 클래스처럼 메서드와 필드를 가질 수 있음

abstract class Animal {
    // 일반 메서드
    public void eat() {
        System.out.println("This animal is eating.");
    }

    // 추상 메서드
    public abstract void makeSound();
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

추상 클래스 VS 일반 클래스

추상 클래스나 일반 클래스나 공통되는 변수나 메서드를 묶어서 만들 수 있고, 상속받아 사용할 수 있다. 그러나 일반 상속(일반 클래스를 상속)과는 다르게, 추상 클래스는 반드시 구현을 필요로 하는 추상 메서드가 존재할 수 있으므로, 모든 하위 클래스에게 구현을 강제할 수도 있습니다. 따라서, 추상 클래스는 객체를 생성할 수 없습니다. 반드시 하위 클래스를 통해서만 객체를 생성할 수 있습니다.

추상클래스는

객체로 생성될 수 없는 클래스로 자식 클래스에서 확장할 수 있도록 만들어진 클래스입니다. 즉, 미완성의 설계도라고 할 수 있으며, 추상 클래스에는 추상 메소드와 일반 메소드 모두 포함시킬 수 있으며, 인스턴스 변수도 가질 수 있습니다. 보통 기본적인 구현은 추상 클래스에서 제공하고, 하위 클래스에서는 고유의 동작을 확장하기 위해서 사용합니다.

이에 반해 인터페이스는 스펙이 정의된 메소드들의 집합입니다. 인터페이스를 구현하는 클래스는 반드시 이 메서드들의 동작을 정의해야 하며, 해당 클래스는 동일한 사용 방법과 동작을 보장할 수 없습니다. 이를 통해 다형성이 가능해집니다. 인터페이스는 보통 클래스 간 상속 관계가 없는 다른 클래스가 동일한 메소드를 구현해야 할 때 사용되는데, 때로는 단순히 클래스 타입을 구분짓기 위해 사용되기도 합니다.

추상 클래스는 반드시 추상 메서드를 갖는가?

항상 그렇지는 않습니다. 추상 클래스는 인스턴스화 할 수 없는 클래스를 의미하며, 일반 메소드만 포함할 수도 있습니다. 이를 통해 공통 기능을 상속받게 할 수 있습니다.

추상 메서드를 갖는 클래스는 반드시 abstract 제어자를 붙여야 하며, 이를 추상 클래스라고 부릅니다. 반대로 추상 클래스가 추상 메서드를 반드시 가져야 하는 것은 아닙니다.

추상 클래스를 쓰는 이유는?

미완성의 설계도라고 할 수 있겠습니다. 인터페이스를 대신 쓸 수도 있지만, 인터페이스에서는 선언부만 있기 때문에, 이를 구현한 하위 클래스에서는 공통적인 기능이 중복되더라도 모두 중복적으로 구현부에 작성을 해야합니다.

  • 공통 기능의 상속

    • 추상 클래스는 하위 클래스들이 공통적으로 사용할 수 있는 메소드와 속성을 정의하며 코드의 중복 줄이고 유지보수성을 향상

  • 하위 클래스의 일관성 유지

    • 추상 클래스는 하위 클래스들이 반드시 구현해야 하는 메서드를 정의할 수 있습니다. 이를 통해 일관된 인터페이스를 유지할 수 있습니다.

  • 인스턴스화 방지

    • 인스턴스로 만들 수 없기 때문에, 반드시 하위 클래스를 통해서만 객체를 생성할 수 있습니다. 이는 특정 클래스가 직접 사용되지 않도록 강제하는데 유용합니다.

추상 클래스에 정의한 일반 메소드를 하위 클래스에서 재정의해서 사용할 수 있는가?

하위 클래스에서 오버라이딩해서 사용할 수 있습니다. 이는 객체지향 프로그래밍에서 다형성을 활용하는 중요한 방법 중 하나입니다. 그러나, 장/단점이 있을 수 있고, 장점보다는 단점이 더 많다고 할 수 있습니다.

추상 클래스의 생성자

추상 클래스는 new 연산자를 통해서 인스턴스화가 불가능하지만, 생성자를 아예 사용하지 못하는 것은 아니다. 추상 클래스내에 생성자 메서드를 정의하고, 자식 클래스에서 super() 키워드를 통해 생성자 메소드를 호출 할 수 있다.

//추상 클래스 선언
public abstract class Pet {
  public int age;

  public Pet(int age){
    this.age = age;
  }

  public abstract void walk();
}

//서브 클래스 선언
public class Dog extends Pet{
  public String name;

  public Dog(int age, String name){
    super(age);
    this.name = name;
  }

  @Override
  public void walk(){
    // ...
  };
}

추상 클래스에서의 final 키워드

  • 메서드의 동작 보장

    • final 메서드를 사용하면, 상위 클래스에서 정의한 메서드의 동작을 하위 클래스에서 변경할 수 없으므로, 메서드의 일관된 동작을 보장할 수 있습니다.

  • 설계의 명확성

    • final 메서드를 사용하여 하위 클래스에서 변경할 수 없는 메서드를 명확히 정의함으로써, 상위 클래스의 설계 의도를 분명히 할 수 있습니다.

  • 성능 최적화

    • 컴파일러는 final 메서드를 호출할 때, 해당 메소드가 오버라이딩 되지 않음을 알기 때문에, 메서드 호출 에 대한 최적화를 할 수 있습니다.

public abstract class Animal {
    // final 메서드는 하위 클래스에서 오버라이딩할 수 없음
    public final void sleep() {
        System.out.println("This animal is sleeping.");
    }

    // 추상 메서드 - 하위 클래스에서 구현해야 함
    public abstract void makeSound();
}

여기서 final은 추상 클래스에서 이미 정의된 메서드를 하위 클래스에서 오버라이딩 할 수 없도록 제어합니다. 하위 클래스에서 변경할 수 없도록 보장하여, 이를 통해 메소드의 일관된 동작을 유지할 수 있습니다.

public final class Utility {
    public static void doSomething() {
        // Implementation
    }
}

// 컴파일 오류: Utility 클래스는 final로 선언되어 상속할 수 없음
// public class ExtendedUtility extends Utility {
// }

부모 클래스를 추상 클래스로써 이용해야 하는 이유

  • 자식 클래스를 업캐스팅해서 다형성을 이용해야 할 필요가 있을 수 있기 때문입니다.

public class Test1 {
    public static void main(String[] args) {
        Unit[] group = new Unit[3];
        group[0] = new Marine();
        group[1] = new Tank();
        group[2] = new DropShip();

        for(Unit u : group) {
            u.move(100, 200);
        }
    }
}

위의 코드에서는, Unit의 자식 클래스들의 인스턴스들을 부모 클래스 타입으로 묶었습니다. 업캐스팅을 통해서 Unit 배열에 자식 객체들을 할당 할 수 있게 됩니다. 그리고 반복문을 통해서 move 메서드를 실행시킵니다. 다형성을 이용한 효율적인 코드를 구성할 수 있습니다.

공통된 필드와 메서드를 통일하는 목적으로는 일반 클래스로도 가능하여, 꼭 추상 클래스만의 고유 용도라고 할 수 없습니다. 그러나 인터페이스는 똑같이 안에 필드를 선언할 수는 있지만, 자동으로 public static final 처리가 되어 공통 상수가 됩니다.

  • 자식 클래스가 반드시 추상 메소드를 구현하도록 만들기 위함입니다.

  • 규격에 맞는 설계 구현

    • 안드로이드 앱 만들 때

    • 안드로이드 SDK에서 제공하는 추상 클래스를 상속받아 필요한 추상 메소드를 구현

Last updated