1. 추상 클래스와 메서드
살다 보면(프로그래밍에서도) 이렇게 말하고 싶을 때가 있습니다: “정확히 어떻게 하는지는 모르겠지만, 반드시 있어야 한다는 건 알아!” 예를 들어, 모든 동물은 소리를 낼 수 있어야 하지만 어떤 소리인지는 동물마다 다릅니다. 바로 이런 경우를 위해 Java에는 추상 클래스와 추상 메서드가 있습니다.
추상 클래스는 직접 인스턴스를 만들 수 없는 클래스입니다(Animal이 추상 클래스라면 new Animal()를 쓸 수 없음). 하지만 이 클래스를 상속받을 수는 있습니다. 추상 클래스는 일반(구현된) 메서드와 추상 메서드를 함께 포함할 수 있습니다. 추상 메서드는 선언만 되어 있고 구현은 없습니다.
추상 메서드는 메서드 본문이 없는 메서드입니다. abstract 키워드로 선언하며, 하위 클래스에서 반드시 구현해야 합니다(하위 클래스 자체가 추상 클래스인 경우는 예외).
실생활 예
동물원을 위한 애플리케이션이 있다고 해봅시다. 모든 동물에 makeSound() 메서드가 있기를 원하지만, 어떤 소리를 내는지는 모릅니다. 그럴 때 추상 클래스를 만듭니다:
public abstract class Animal {
public abstract void makeSound(); // 추상 메서드
}
그리고 구체적인 동물들이 각자 방식으로 이 메서드를 구현합니다:
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("야옹!");
}
}
이제 누군가 new Animal()을 만들려고 하면, 컴파일러는 곧바로 이렇게 말할 것입니다: “미안하지만, 추상 동물은 자연에 존재하지 않아!” 이것은 유용합니다. 프로그램에는 구체적인 동물, 구체적인 동작만 존재하도록 보장할 수 있으니까요.
2. 추상을 통한 다형성
추상화는 객체 집합을 위한 공통 인터페이스를 도출하는 것입니다. 추상 클래스는 바로 그 공통 인터페이스를 규정합니다. 즉, 모든 하위 클래스가 반드시 구현해야 하는 메서드를 지정합니다.
다형성과 추상화는 함께 작동합니다. 추상 클래스는 모든 하위 클래스에 필요한 메서드가 있음을 보장하고, 다형성은 상위 타입 참조를 통해 그 메서드들을 호출할 수 있게 해 줍니다.
예: 동물원 만들기
작은 동물원을 만들어 봅시다. 추상 클래스 Animal과 그 하위 클래스 몇 개가 있습니다:
public abstract class Animal {
public abstract void makeSound();
}
public class Cow extends Animal {
@Override
public void makeSound() {
System.out.println("음메!");
}
}
public class Duck extends Animal {
@Override
public void makeSound() {
System.out.println("꽥꽥!");
}
}
이제 동물 배열을 만들 수 있습니다:
Animal[] zoo = {
new Dog(),
new Cat(),
new Cow(),
new Duck()
};
for (Animal animal : zoo) {
animal.makeSound(); // 각 동물에 대해 "올바른" 메서드가 호출된다
}
배열의 각 객체는 구체적인 동물이지만, 코드 입장에서는 그냥 Animal입니다. 추상화와 다형성 덕분에 각 객체에 makeSound() 메서드가 존재하며 올바르게 동작함을 확신할 수 있습니다.
3. 다형성을 위한 추상 클래스 활용
좀 더 실용적인 예를 보겠습니다. 어떤 회사의 직원 관리를 위한 애플리케이션을 만든다고 가정합시다. 직원 유형은 매니저, 개발자, 테스터 등 다양합니다. 모두 공통 메서드 work()를 갖지만, 수행 방식은 서로 다릅니다.
추상 클래스 Employee
public abstract class Employee {
protected String name;
public Employee(String name) {
this.name = name;
}
public abstract void work();
}
구체 하위 클래스
public class Manager extends Employee {
public Manager(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " — 팀을 이끈다.");
}
}
public class Developer extends Employee {
public Developer(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " — 코드를 작성한다.");
}
}
public class Tester extends Employee {
public Tester(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " — 애플리케이션을 테스트한다.");
}
}
다형성 사용
이제 직원 배열을 만들고 각자에게 work()를 호출할 수 있습니다:
Employee[] employees = {
new Manager("안나"),
new Developer("이반"),
new Tester("마리아")
};
for (Employee e : employees) {
e.work();
}
결과:
안나 — 팀을 이끈다.
이반 — 코드를 작성한다.
마리아 — 애플리케이션을 테스트한다.
주의: 우리는(그리고 알 필요도 없습니다!) 반복문에서 정확히 어떤 직원 타입인지는 모릅니다. 그냥 work()를 호출하면, 각 객체가 자기 할 일을 수행합니다.
4. 유용한 포인트
메서드 구현 보장
추상 클래스는 모든 하위 클래스가 필요한 메서드를 구현하도록 강제합니다. 하위 클래스에서 추상 메서드 구현을 빠뜨리면, 컴파일러가 곧바로 말할 것입니다: “반드시 구현해야 해!”
범용 인터페이스
추상 타입(Employee[], List<Animal>)의 배열이나 리스트로 동작하는 코드는 완전히 범용적일 수 있습니다. 새로운 하위 클래스를 추가해도 핵심 코드를 변경할 필요가 없습니다.
엉뚱한 객체로부터의 보호
추상 클래스는 직접 인스턴스화할 수 없기 때문에, 필요한 메서드를 구현하지 않은 “정체불명” 타입의 객체가 실수로 만들어질 일이 없습니다.
이론과 문법: 추상 클래스/메서드 선언 방법
- class 앞에 abstract 키워드를 붙이면 추상 클래스를 선언합니다.
- abstract 키워드로 추상 메서드를 선언하며, 본문 없이(세미콜론만) 선언합니다.
- 하나라도 추상 메서드가 있으면 그 클래스는 반드시 추상 클래스여야 합니다.
- 추상 클래스를 상속하는 클래스는 모든 추상 메서드를 구현해야 하며, 그렇지 않다면 그 클래스 자체도 추상이어야 합니다.
도식
public abstract class Animal {
public abstract void makeSound();
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("야옹!");
}
}
5. 추상 클래스를 다룰 때 흔한 실수
오류 №1: 추상 클래스의 인스턴스를 만들려고 함.
new Animal() 같은 코드는 컴파일되지 않습니다. 추상 클래스는 부품 없이 가구 조립 설명서를 보는 것과 같습니다. 구체 하위 클래스가 나타나기 전에는 객체를 만들 수 없습니다.
오류 №2: 하위 클래스에서 추상 메서드 구현을 누락.
추상 메서드를 선언해 놓고 하위 클래스에서 구현하지 않았다면(그리고 그 클래스도 추상이 아니라면), 컴파일러가 오류를 보고합니다.
오류 №3: 접근 제한자를 깜빡함.
재정의된 메서드는 상위 클래스보다 더 엄격한 접근 제한자를 가질 수 없습니다. 예를 들어, 추상 메서드가 public이었다면 구현도 반드시 public이어야 합니다(protected나 private가 아님).
오류 №4: 본문이 있는 추상 메서드를 시도함.
abstract 메서드는 본문을 가질 수 없습니다. 그렇지 않으면 컴파일러가 말할 것입니다: “추상으로 할지, 구현할지 하나만 정해!”
오류 №5: 정적 메서드에는 다형성이 적용되지 않음.
다형성은 인스턴스 메서드에만 동작합니다. 정적 메서드는 오버라이드되지 않고 숨겨질 뿐이므로, 호출 시 동작은 실제 객체가 아니라 변수의 타입에 따라 결정됩니다.
GO TO FULL VERSION