인터페이스(Interface)와 추상 클래스(Abstract Class)는 코드의 재사용성과 다형성을 구현하는 데 있어 핵심적인 역할을 합니다.
하지만 언제 인터페이스를 사용하고, 언제 추상 클래스를 사용해야 하는지 혼란스러울 수 있습니다.
이 글에서는 인터페이스와 추상 클래스의 차이점을 명확히 이해하고, 적절한 상황에서 올바르게 사용하는 방법을 알아보겠습니다.
인터페이스란?
인터페이스는 모든 메소드가 추상 메소드로 구성되어 있으며, Java 8 이후부터는 default 메소드와 static 메소드를 포함할 수 있게 되었습니다. 인터페이스는 특정 클래스가 반드시 구현해야 할 메소드를 정의합니다
인터페이스는 리모컨과 같습니다.
리모컨은 다양한 전자 기기(예: TV, 에어컨, 오디오 시스템)를 제어할 수 있는 버튼(메소드)들의 집합입니다.
각 버튼은 특정 기능(예: 켜기, 끄기, 볼륨 조절)을 정의하지만, 실제 기능의 구현은 연결된 기기에 따라 달라집니다.
즉, '켜기' 버튼은 TV를 켜는 방법과 에어컨을 켜는 방법 사이에 차이가 있을 수 있습니다.
인터페이스는 이러한 '버튼'들을 정의하지만, 실제 '버튼'이 어떻게 작동하는지는 구현하는 객체(기기)에 달려 있습니다.
public interface RemoteControl {
void powerOn();
void powerOff();
void volumeUp();
void volumeDown();
}
public class Television implements RemoteControl {
public void powerOn() {
// TV를 켜는 구체적인 코드
}
public void powerOff() {
// TV를 끄는 구체적인 코드
}
// 볼륨 관련 메소드 구현 ...
}
public class AirConditioner implements RemoteControl {
public void powerOn() {
// 에어컨을 켜는 구체적인 코드
}
public void powerOff() {
// 에어컨을 끄는 구체적인 코드
}
// 볼륨 관련 메소드 구현 ...
}
추상 클래스란?
추상 클래스는 하나 이상의 추상 메소드를 포함하며, 인스턴스를 직접 생성할 수 없습니다. 추상 클래스는 상속을 통해 자식 클래스에 의해 확장되어야 하며, 자식 클래스는 모든 추상 메소드를 구현해야 합니다.
추상 클래스는 동물원에 있는 동물과 같습니다.
모든 동물은 '먹기'나 '자기' 같은 공통적인 행동을 공유하지만, 각 동물마다 이러한 행동을 수행하는 구체적인 방법은 다를 수 있습니다.
예를 들어, 고기를 먹는 동물과 풀을 먹는 동물이 있을 수 있습니다.
추상 클래스는 이러한 공통적인 행동을 정의하지만, 실제 행동의 구현은 상속받는 서브클래스에 의해 결정됩니다.
public abstract class Animal {
public abstract void eat();
public void sleep() {
// 모든 동물이 공통으로 사용할 수 있는 '자기' 메소드
System.out.println("This animal is sleeping.");
}
}
public class Lion extends Animal {
public void eat() {
// 사자가 먹는 구체적인 방식을 구현
System.out.println("Lion is eating meat.");
}
}
public class Elephant extends Animal {
public void eat() {
// 코끼리가 먹는 구체적인 방식을 구현
System.out.println("Elephant is eating plants.");
}
}
Vehicle 추상 클래스는 startEngine이라는 추상 메소드와 stopEngine이라는 일반 메소드를 정의하고 있습니다. Car 클래스는 Vehicle을 상속받아 startEngine 메소드를 구현합니다. 이 예시에서 추상 클래스는 공통적인 기능(stopEngine)을 제공하면서도, 특정 기능(startEngine)에 대한 구현을 강제합니다.
주요 차이점
- 목적: 인터페이스는 구현해야 할 "메서드"를 정의하는 반면, 추상 클래스는 일부 기능을 상속하고 일부 기능을 강제하는 데 사용됩니다.
- 다중 상속: 클래스는 여러 인터페이스를 구현할 수 있지만, 하나의 추상 클래스만 상속받을 수 있습니다.
- 메소드 구현: 인터페이스는 Java 8 이전에는 구현된 메소드를 포함할 수 없었지만, 추상 클래스는 구현된 메소드와 추상 메소드를 모두 포함할 수 있습니다.
- 상태 관리: 인터페이스는 상태(필드)를 가질 수 없지만, 추상 클래스는 상태(필드)를 가질 수 있습니다.
| 특성 | 인터페이스 (Interface) | 추상 클래스 (Abstract Class) |
|---|---|---|
| 메소드 구현 | Java 8부터 default 메소드와 static 메소드에 한해서 구현 가능 | 추상 메소드와 구현된 메소드 모두 포함 가능 |
| 상태 관리 | 상수(final 변수)만 포함 가능 | 변수를 포함할 수 있으며 상태(값)를 유지할 수 있음 |
| 다중 상속 | 지원 (클래스가 여러 인터페이스를 구현할 수 있음) | 지원하지 않음 (클래스가 하나의 추상 클래스만 상속받을 수 있음) |
| 생성자 | 없음 | 있음 (추상 클래스를 직접 인스턴스화할 수는 없지만, 생성자를 통해 초기화 코드를 포함할 수 있음) |
| 접근 제어자 | 모든 메소드는 public (Java 9부터 private 메소드도 가능) | 메소드에 다양한 접근 제어자 사용 가능 |
| 사용 사례 | 서로 관련 없는 클래스들이 특정 메소드를 구현하도록 강제할 때 | 관련된 클래스들 사이에 코드를 공유하고, 일부 메소드 구현을 강제할 때 |
| 인스턴스화 | 불가능 | 불가능 (하지만 추상 클래스를 상속받은 서브 클래스는 인스턴스화할 수 있음) |
powerOn, volumeUp 등과 같이 서로 다른 클래스들이 공통의 동작을 공유해야 할 때 사용합니다.Animal 추상 클래스가 eat과 같은 공통 메소드의 구현을 제공하고, 서브클래스인 Lion, Elephant 등이 이를 상속받는 경우입니다.결국, 인터페이스는 "할 수 있는 것"을 정의하는 데 초점을 맞추고,
추상 클래스는 "하는 방법"을 일부 공유하면서도 확장을 허용하는 데 사용됩니다.
따라서 설계의 목적과 요구 사항에 따라 적절한 선택을 해야 합니다.
인터페이스와 추상 클래스는 Java에서 코드의 재사용성과 유연성을 높이는 데 사용됩니다. 인터페이스는 주로 다중 상속을 지원하고, 구현해야 할 메소드를 정의할 때 사용됩니다. 반면, 추상 클래스는 공통 기능을 제공하면서 특정 메소드의 구현을 자식 클래스에 강제할 때 사용됩니다. 각각의 특성을 이해하고 상황에 맞게 적절히 선택하는 것이 중요합니다.