Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

Java 기본 메소드(Default Method)로 다이아몬드 문제 해결하기

상속(Inheritance)은 두 클래스 간의 관계로, 한 클래스가 다른 클래스의 속성과 기능을 물려받는 것을 의미합니다. 이 관계는 extends 키워드를 사용하여 정의할 수 있습니다.

public class A extends B {
}

속성을 상속받는 클래스를 하위 클래스(sub class) 또는 자식 클래스(child class)라고 하며, 속성을 제공하는 클래스를 상위 클래스(super class) 또는 부모 클래스(parent class)라고 부릅니다.

상속이 이루어지면 하위 클래스 객체 내부에 상위 클래스 멤버들의 복사본이 생성됩니다. 따라서 하위 클래스 객체를 통해 두 클래스의 모든 멤버에 접근할 수 있습니다.

다중 상속(Multiple Inheritance)이란?

상속에는 여러 가지 유형이 있습니다. 단일 상속(single), 다단계 상속(multilevel), 계층적 상속(hierarchical), 다중 상속(multiple), 그리고 혼합 상속(hybrid)이 대표적입니다.

다중 상속은 하나의 클래스가 여러 클래스의 속성을 동시에 상속받는 형태입니다. 즉, 하나의 자식 클래스가 n개의 부모 클래스를 가질 수 있는 구조입니다. 그러나 Java는 클래스 간의 다중 상속을 지원하지 않습니다.

다이아몬드 문제(Diamond Problem)

만약 Java가 다중 상속을 지원한다고 가정해 보겠습니다. 이 가정 아래에서 다음 예제를 살펴봅시다.

예제

먼저 추상 메소드를 하나 가진 Sample이라는 추상 클래스가 있다고 합시다.

public abstract class Sample {
    public abstract void demo();
}

같은 패키지(폴더) 안에 이 클래스를 상속받아 추상 메소드 demo()를 구현하는 두 개의 클래스가 존재합니다.

public class Super1 extends Sample {
    public void demo() {
        System.out.println("demo method of super1");
    }
}
public class Super2 extends Sample {
    public void demo() {
        System.out.println("demo method of super2");
    }
}

Java가 다중 상속을 지원한다는 가정하에, 우리는 Super1과 Super2 두 클래스를 모두 상속받으려고 시도합니다.

public class SubClass extends Super1, Super2 {
    public static void main(String args[]) {
        SubClass obj = new SubClass();
        obj.demo();
    }
}

상속의 기본 규칙에 따르면, 하위 클래스 객체 내부에 두 개의 demo() 메소드 복사본이 생성되어야 합니다. 결과적으로 하위 클래스는 이름과 매개변수가 완전히 동일한 프로토타입을 가진 두 개의 메소드를 갖게 됩니다.

이 상태에서 하위 클래스의 객체로 demo() 메소드를 호출하면, 컴파일러는 어떤 메소드를 호출해야 할지 판단할 수 없는 모호성(ambiguity) 문제에 직면하게 됩니다. 이것이 바로 Java에서 말하는 다이아몬드 문제입니다.

바로 이러한 이유 때문에 Java는 다중 상속을 지원하지 않습니다. 즉, 하나의 클래스는 둘 이상의 다른 클래스를 확장(extends)할 수 없으며, 이를 시도하면 컴파일 타임 오류가 발생합니다.

컴파일 타임 오류

위와 같이 여러 클래스를 동시에 상속받는 코드를 컴파일하면 다음과 같은 오류가 발생합니다.

MultipleInheritanceExample.java:9: error: '{' expected
public class MultipleInheritanceExample extends MyInterface1, MyInterface2{
                                                                              ^
1 error

해결 방법: 인터페이스와 기본 메소드(Default Method)

Java에서도 인터페이스(interface)기본 메소드(default method)(Java 8부터 도입)를 활용하면 다중 상속의 효과를 얻을 수 있습니다.

Java 8부터 인터페이스 내에 기본 메소드를 정의할 수 있게 되었습니다. 다른 추상 메소드와 달리, 기본 메소드는 인터페이스 내부에 기본 구현체(body)를 포함하는 메소드입니다. 따라서 해당 인터페이스를 이미 구현하고 있는 클래스에서 반드시 오버라이드(재정의)하지 않아도 됩니다.

여기서 흥미로운 점은, 서로 다른 두 인터페이스에 동일한 이름과 시그니처를 가진 기본 메소드를 정의하고, 하나의 클래스가 이 두 인터페이스를 모두 구현(implements)할 수 있다는 것입니다.

다만 이 경우, 클래스 내부에서 해당 기본 메소드를 반드시 명시적으로 오버라이드해야 하며, 호출 시에는 인터페이스명.super.메소드명() 형태로 어떤 인터페이스의 메소드인지 지정해 주어야 합니다. 이를 통해 다이아몬드 문제의 모호성을 해결할 수 있습니다.

예제 코드

interface MyInterface1 {
    public static int num = 100;
    public default void display() {
        System.out.println("display method of MyInterface1");
    }
}
interface MyInterface2 {
    public static int num = 1000;
    public default void display() {
        System.out.println("display method of MyInterface2");
    }
}
public class InterfaceExample implements MyInterface1, MyInterface2 {
    public void display() {
        MyInterface1.super.display();
        // 또는,
        MyInterface2.super.display();
    }
    public static void main(String args[]) {
        InterfaceExample obj = new InterfaceExample();
        obj.display();
    }
}

실행 결과

display method of MyInterface1
display method of MyInterface2

핵심 정리: Java는 클래스 간 다중 상속으로 인한 모호성, 즉 다이아몬드 문제를 방지하기 위해 다중 상속을 허용하지 않습니다. 대신 인터페이스의 기본 메소드를 활용하고, 충돌 발생 시 인터페이스명.super.메소드() 방식으로 명확하게 호출 대상을 지정함으로써 안전하게 다중 상속과 유사한 구조를 구현할 수 있습니다.