자바는 왜 다중 상속을 막았을까? 다이아몬드 문제 파헤치기
자바가 클래스 다중 상속을 허용하지 않는 이유를 다이아몬드 문제와 인터페이스 예제로 설명한 글
다이아몬드 문제(The Diamond Problem)
- 최상위 부모 클래스
A가 있습니다. - 두 개의 자식 클래스
B,C가 모두A를 상속받습니다. B와C는A의 특정 메서드를 각각 재정의(Override) 합니다.- 또 다른 자식 클래스
D가B와C를 동시에 상속받으려고 합니다.

위처럼 Vehicle을 상속받아 Chariot, Boat를 만들고, 다시 이 둘을 동시에 상속하는 AmphibiousChariot를 상상해 보면 문제가 보입니다. Chariot와 Boat가 서로 다른 fillFuel() 메서드를 오버라이딩했다면, 수륙양용 전차는 어떤 메서드를 상속받아야 할지 결정할 수 없게 됩니다.
해결책
자바는 이 문제를 해결하기 위해 클래스는 단일 상속만 허용하고, 대신 인터페이스를 여러 개 구현해서 다중 상속과 비슷한 효과를 내도록 설계했습니다.
구현이 없는 인터페이스
// 만약 자바가 다중 상속을 허용한다면... (가상 코드)
class Chariot extends Vehicle {
@Override
public void fillFuel() {
System.out.println("마차에 건초를 공급합니다.");
}
}
class Boat extends Vehicle {
@Override
public void fillFuel() {
System.out.println("보트에 돛을 답니다.");
}
}
class AmphibiousChariot extends Chariot, Boat {
public void refuel() {
// Chariot의 fillFuel()? Boat의 fillFuel()?
fillFuel();
}
}
인터페이스는 선언만 있고 구현이 없기 때문에, 여러 인터페이스를 implements 하더라도 어떤 부모 구현을 가져와야 할지 고민하지 않고 구현 클래스에서 직접 메서드를 작성하면 됩니다.
default 메서드의 경우
interface HasGasoline {
default void fillFuel() {
System.out.println("주유소에서 휘발유를 채웁니다.");
}
}
interface HasElectricity {
default void fillFuel() {
System.out.println("충전소에서 전기를 충전합니다.");
}
}
class HybridCar implements HasGasoline, HasElectricity {
@Override
public void fillFuel() {
HasGasoline.super.fillFuel();
HasElectricity.super.fillFuel();
System.out.println("하이브리드 자동차의 연료를 모두 채웁니다.");
}
}
Java 8부터는 인터페이스에 default 메서드가 도입되어 인터페이스 내부에도 구현을 둘 수 있게 됐습니다. 이때 서로 다른 인터페이스가 같은 시그니처의 default 메서드를 제공하면 모호성이 다시 생깁니다.
자바는 이 경우 구현 클래스가 반드시 직접 오버라이드해서 모호성을 제거하도록 강제합니다.
결론
자바는 다중 상속이 주는 유연함보다, 그로 인해 발생할 수 있는 예측 불가능성과 복잡성이 더 큰 비용이라고 판단했습니다.
즉, 인터페이스를 통해 다형성의 장점은 유지하면서도, 구현 충돌이라는 모호함은 차단하는 방향을 택한 것입니다. 이런 설계 덕분에 자바는 단순함, 명확성, 안정성을 더 잘 유지할 수 있습니다.