숏컷 코드
java
class Animal {
void speak() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("woof");
}
}문법
어떤 관계를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| is-a 관계가 분명함 | extends |
| 상위 타입으로 여러 구현체 처리 | 다형성 |
| 재정의 의도 검증 | @Override |
| 단순 재사용 목적 | 조합(composition) 우선 |
extends와 override: 상속의 기본 형태
상속은 기존 클래스의 상태와 동작을 이어받아 더 구체적인 클래스를 만드는 방식입니다. @Override는 단순한 장식이 아니라 의도한 재정의가 맞는지 컴파일러가 검증하게 해 주는 안전장치입니다. 이 어노테이션이 없으면 오타로 인해 재정의가 아닌 새 메서드를 만드는 실수가 조용히 발생할 수 있습니다.
java
class Shape {
double area() { return 0; }
}
class Circle extends Shape {
private final double radius;
Circle(double radius) { this.radius = radius; }
@Override
double area() {
return Math.PI * radius * radius;
}
}다형성: 상위 타입으로 하위 객체 다루기
다형성의 핵심은 Animal a = new Dog();처럼 상위 타입 참조로도 하위 객체를 다룰 수 있다는 것입니다. 실제 호출은 런타임 타입에 맞는 메서드가 선택됩니다. 이 덕분에 새 타입을 추가해도 기존 코드를 바꾸지 않을 수 있습니다.
java
List<Animal> animals = List.of(new Dog(), new Cat(), new Dog());
// 각 객체의 실제 타입에 맞는 speak()가 호출됨
for (Animal a : animals) {
a.speak(); // Dog면 "woof", Cat이면 "meow"
}상속은 언제 과해지는가
상속은 is-a 관계가 분명할 때 잘 맞습니다. 단순히 코드 몇 줄을 재사용하려는 목적만으로 상속을 쓰면 클래스 계층이 왜곡됩니다. 실무에서는 상속보다 조합(composition)을 더 선호하는 경우가 많습니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
| is-a 관계가 명확하고 공통 동작 공유 | extends 상속 |
| 재정의 의도를 컴파일러에게 검증 | @Override |
| 상위 타입으로 여러 구현체 처리 | 다형성 |
| 코드 재사용이 목적 | 조합(composition) 우선 검토 |
| is-a 관계 불분명 | 상속 대신 위임·조합 |
주의할 점
"코드 복붙 줄이기"를 목적으로 상속을 쓰면 클래스 계층이 자연스럽지 않아집니다.
java
// ❌ 재사용 목적의 잘못된 상속 — Stack이 Vector의 일종이 아님에도 상속
// (Java 표준 라이브러리의 실제 실수)
class Stack<E> extends Vector<E> { ... }
// ✅ 조합으로 재사용 — has-a 관계
class Stack<E> {
private final Deque<E> storage = new ArrayDeque<>();
public void push(E item) { storage.push(item); }
public E pop() { return storage.pop(); }
}참고 링크
3 sources