자바는 다중 상속을 허용하지 않는다.

Father와 Mother 클래스가 Person 클래스를 extends 했고, 추상 메서드 funcA를 각각 구현했다.
Child가 Father와 Mother를 둘 다 extends 한다면, Child.funcA()는 어떤 메서드를 상속받아야 할 지 알 수 없다. (다중상속의 모호성)
이를 다이아몬드 문제라고 부르며, 다중상속을 허용하지 않는 이유이다.
하지만 인터페이스의 다중 상속은 허용된다. 한 인터페이스가 여러 인터페이스를 extends 할 수 있고, 클래스는 여러 인터페이스를 implements 할 수 있다.
interface A { ... }
interface B { ... }
interface C extends A, B { ... }
class Class implements A, B { ... }
기존의 인터페이스는 추상 메소드만 정의할 수 있었지만, Java 8부터 default 키워드를 사용해 본문이 있는 메서드 또한 정의할 수 있게 되었다.
default method는 '하위 호환성' 때문에 만들어졌다.
기존 인터페이스에 새로운 메서드를 추가해야 하는 경우, 해당 인터페이스를 구현하는 모든 클래스에 새 메서드를 구현해야 했다. 이는 확장과 호환성과 기존 코드 변경에 큰 문제가 있다.
default method를 사용하면 해당 문제를 해결할 수 있다.
interface A {
void funcA();
default void funcB() {
System.out.println("This is default method.");
}
}
(1) 역호환성 유지 : 기존 인터페이스 확장하고 새 메서드 추가할 때, 이미 구현한 클래스는 새 메서드 구현하지 않아도 됨.
(2) 기본 구현 제공
(3) 다중 상속 문제 해결 : default method를 통해 인터페이스에서 다중 상속과 유사한 기능을 제공할 수 있음.
클래스는 다이아몬드 문제 때문에 다중 상속을 허용하지 않는다. 두 상위 클래스에 같은 메서드가 있을 경우, 하위 클래스에서 어떤 메서드를 사용해야 할 지 알 수 없다. (다중 상속의 모호성)
default method 또한 동일한 문제가 발생하는데, 이를 해결하기 위해서는 자식 클래스에서 반드시 해당 메서드를 override 하거나, 어떤 super 메서드를 사용할 지 명시해야 한다.
interface Mother {
void funcA();
default void funcB() {
System.out.println("I'm a mother");
}
default void funcM() {
System.out.println("mother method");
}
}
interface Father {
void funcA();
default void funcB() {
System.out.println("I'm a father");
}
default void funcF() {
System.out.println("father method");
}
}
class Child implements Mother, Father {
public void funcA() {
System.out.println("AA");
}
public void funcB() {
// System.out.println("default method");
Mother.super.funcB();
}
}
public class Main
{
public static void main(String[] args) {
Child child = new Child();
child.funcA();
child.funcB();
child.funcM();
child.funcF();
}
}
default method를 통해 인터페이스에서 다중 상속을 구현할 때 발생하는 다이아몬드 문제는 해당 메서드를 override 하거나, 어떤 super 메서드를 사용할 지 명시를 통해 해결할 수 있다고 하였다.
그렇다면, 클래스의 다중 상속도 이 방법을 통해 해결할 수 있는 것 아닐까??
Java 8 에서 default method를 추가할 때 '명시적으로 오버라이딩 해야 한다'는 규칙을 추가함으로써 모호성을 해결 할 수 있는 구조로 설계되었다.
인터페이스는 상태(state)가 없고 논리적 행동(behavior)만을 제공하므로, 충돌 시 컴파일 에러를 내고, 명시적 해결로써 해결이 가능하다.
상태 : 객체 내부에 저장된 데이터 값, 필드(멤버 변수)
하지만 클래스는
(1) 필드(멤버 변수)도 상속이 가능하다.
두 부모 클래스에서 동일한 이름의 필드가 있을 경우 충돌이 가능한다.
(2) 생성자가 충돌할 수 있다.
인터페이스는 생성자를 가질 수 없다.
클래스는 생성자를 가질 수 있으므로 자식 클래스에서 어떤 부모의 생성자 호출할 지 명확하지 않다.
이처럼 클래스는 인터페이스보다 복잡한 상속 구조로 인해 변경이 위험하고, 의도 파악이 어렵다.
즉 자바는 클래스 다중 상속이 야기하는 복잡성과 모호성을 방지하기 위해 이를 금지했다. 반면, 인터페이스는 충돌이 있어도 오버라이딩을 통해 해결할 수 있도록 설계했다.
이로 인해 자바는 단순한 객체 모델을 유지하면서, 인터페이스 다중 상속을 통해 유연한 코드 구조를 만들 수 있게 되었다.
참고