본문 바로가기
Java

[Java] 다중 상속이 가지는 문제

by soro.k 2023. 2. 3.

 

들어가기 전에

 

자바가 다중 상속을 지원하지 않는 이유는 무엇일까?

James Gosling이 작성한 자바 백서 <Java:an Overview>에 따른 자바의 특징을 먼저 살펴 보자.

 

출처 : <Java:an Overview>, James Gosling, February 1995

 

자바는

  • 단순하다.
  • 객체 지향적이다.
  • 분산 환경에서 사용된다.
  • 인터프리터 언어이다.
  • 견고하며 안전하다.
  • 아키텍처에 중립적이다.
  • 이식성이 있다.
  • 높은 성능을 제공한다.
  • 멀티스레드를 지원한다.
  • 동적으로 처리가 가능한 언어이다.

 

 

이중 오늘의 주제와 연관된 특징은 바로 "SIMPLE"이다. 

<Introduction to Java TM Technology> Oracle
Primary characteristics of the Java programming language include a simple language that can be programmed without extensive programmer training while being attuned to current software practices. The fundamental concepts of Java technology are grasped quickly; programmers can be productive from the very beginning.

출처 : https://www.oracle.com/java/technologies/introduction-to-java.html

 

자바는 폭넓은 교육 없이도 프로그래밍 할 수 있도록 만들어진 언어이다.  그래서 자바 기술의 기본 컨셉은 어떤 사람이 봐도 쉽게 파악할 수 있기 때문에 개발자들은 자바를 처음 사용하는 순간부터 생산적인 프로그래밍이 가능하다. 그렇다면 자바는 이 특징을 갖기 위해 어떤 노력을 했을까?

 

자바 개발자들은 최대한 쉽게 프로그래밍할 수 있는 언어를 만들고자 했다. 당시 대부분의 개발자들은 C를 사용하고, C++로 객체 지향 프로그래밍을 했는데 러닝 커브가 높고 복잡한 언어이다 보니 사용하기 쉽지 않아서 자바가 추구하는 방향과는 많이 달랐다. 하지만 아무래도 많은 개발자들이 사용하는 언어이다 보니 최대한 C++의 문법에 기반을 두면서 개발을 시작하게 됐다. 실제로 C 계열 프로그래밍 언어 목록을 보면 자바가 존재한다.

 

 

출처 :&nbsp;&nbsp;<Java:an Overview>, James Gosling, February 1995

대신 자바는 C++에서 자주 사용되지 않고 이해하기 어려우면서 혼란스러운 기능들을 배제시켰다. 또한 수동으로 메모리를 관리했어야 하는 어려움에서 벗어나게 하기 위해 GC를 도입하는 등의 노력을 통해 "단순"하다는 특징을 가질 수 있었다. 그리고 위의 스크린샷 속 구문을 보면 배제시켰던 C++의 기능들을 나열하는데 이곳에서 바로 multiple inheritance(다중 상속) 발견할 수 있다.

 

다시 첫 질문으로 돌아가보자.  지금까지 살펴본 내용으로 정리하자면 자바가 다중 상속을 지원하지 않는 이유 다중 상속이 자바가 구현하고자 하는 언어의 특징과 맞지 않았기 때문이다. 도대체 다중 상속이 어떻길래 맞지 않는다는 걸까? 이제는 어떤 문제점으로 다중 상속이 배제되었는지 알아볼 차례이다.

 

 

다중 상속의 모호성

ClassA를 상속받는 두 개의 클래스 ClassB, ClassC가 있다.

public class ClassA {
    public void perform() {
        System.out.println("ClassA입니다.")
    }
}

public class ClassB extends ClassA {
    @Override
    public void perform() {
        System.out.println("ClassB입니다.")
    }
}

public class ClassC extends ClassA {
    @Override
    public void perform() {
        System.out.println("ClassC입니다.")
    }
}

 

만약 지금의 상속 관계에서 ClassD가 ClassB와 ClassC를 상속받으면 어떻게 될까? 컴파일러는 ClassD가 상속받고자 하는 상위클래스에서 똑같은 이름을 가진 두 개의 메서드 중 어떤 것을 선택해야 할지 모르기 때문에 에러가 발생한다. 이게 바로 다중 상속으로 인해 발생할 수 있는 문제모호성(ambiguous)인 것이다. 그리고 이 문제를 아래 그림과 같이 다이아몬드 구조를 가진 상속 문제라는 의미로 다이아몬드 문제라고 한다.

 

출처 :&nbsp;https://www.scaler.com/topics/multiple-inheritance-in-java/

 

 

우리는 이제 자바는 다이아몬드 문제, 즉, 모호성으로 인한 혼란이 오지 않도록 다중 상속을 지원하지 않는다는 사실을 알게 되었다. 하지만 더 알아둬야 할 것이 있다. 자바 8부터 인터페이스에 Default Method를 도입하면서 인터페이스를 통한 다중 상속이 가능해졌다는 것이다.

 

다음은 Default Method를 이용해서 다이아몬드 상속을 구현한 코드이다.

public interface President {
    default String work() {
        return "President is working.";
    }
}

public interface TeamA extends President {
    @Override
    default String work() {
       return "TeamA is working.";
    }
}

public interface TeamB extends President {
    @Override
    default String work() {
        return "TeamB is working.";
    }
}

public interface Worker extends TeamA, TeamB {
	...
}

 

최하위 인터페이스인 Worker에서 어떻게 이 모호성을 극복할 수 있을까?

 

 

 

자바가 다중 상속을 해결하는 방법

첫 번째두 상위 인터페이스에서 사용하는 메서드를 오버라이딩해서 구현부를 하위 인터페이스에 맞게 변경하는 것이다. 클래스로 구현할 때도 마찬가지로 적용한다.

public interface Worker extends TeamA, TeamB {
    @Override
    default String work() {
        return "Worker is working.";
    }
}

public class Worker implements TeamA, TeamB {
	...
    @Override
    public String work() {
        return "Worker is working.";
    }
}

 

두 번째상속받는 상위 클래스의 메서드를 사용하는 것이다.

// 1. TeamA의 메서드 이용하기
public interface Worker extends TeamA, TeamB {
    @Override
    default String work() {
        return TeamA.super.work();
    }
}

// 2. TeamB의 메서드 이용하기
public interface Worker extends TeamA, TeamB {
    @Override
    default String work() {
        return TeamB.super.work();
    }
}

// 3. TeamA와 TeamB의 메서드 이용하기
public interface Worker extends TeamA, TeamB {
    @Override
    default String work() {
        return TeamA.super.work() + " " + TeamB.super.work();
    }
}

 

 

 

이렇게 자바의 특징부터 다중 상속의 문제, 해결 방안까지 알아봤다. 다중 상속에 대해 공부를 하다 보면 "그냥 사용하지 마세요."라는 글과 댓글을 많이 보게 된다. 굳이 문제가 발생하는 상속 관계를 설정하지 말고 최대한 다른 방식으로 구현하라는 의미인 걸까? 그렇다기에는 솔루션이 마련되어 있는데 "그냥" 사용하지 말라는 의견을 어떻게 받아들여야 할지 고민했다. 그래서 슬랙에 질문을 남긴 결과, 내 사고의 편협함을 다시 한 번 깨닫게 되었다. 

 

다중 상속의 근본은 상속이다. 다중 상속을 구현할 때는 다중 상속이 가지는 문제점 뿐만 아니라 상속 관계에 있는 두 인터페이스가 정말 상속으로 구현되어야 할 케이스인지, 이렇게 구현했을 때의 문제점은 무엇일지, 상속이 아닌 조합으로는 해결할 수 없는지, 추후에 유지보수할 때 문제가 없을지 등 여러 가지를 고려해야 하는 것이다. 

 

그러니까 무엇이든 "그냥"은 없다. 다중 상속의 문제, 더 나아가 상속이 가지는 문제점까지 고려하면서 코드를 작성하자.

 

 

정리

  • 자바는 구현의 모호성으로 인한 혼란을 방지하기 위해 다중 상속을 지원하지 않는다.
  • 자바 8부터 인터페이스에 Default Method가 도입되면서 인터페이스를 통한 다중 상속이 가능하게 됐다.
  • 다중 상속을 할 때는 최하위 클래스 혹은 인터페이스에 오버라이딩한 메서드의 구현부를 새로 작성하거나 상위 인터페이스.super.Method()와 같은 방식으로 상위 메서드를 활용해 다이아몬드 문제를 해결한다.

 

면접 예상 질문

  • 자바가 다중 상속을 지원하지 않는 이유는 무엇인가요?
  • 다이아몬드 문제는 어떤 것이고, 어떤 방식으로 해결할 수 있나요?

 

 

참고

'Java' 카테고리의 다른 글

[Java] Object.equals()  (0) 2023.02.14
[Java] Socket에 관하여  (0) 2023.02.08
[Java] 컴포지션을 선택하는 이유  (0) 2023.01.31
[Java] 객체를 생성하는 다양한 방법  (0) 2023.01.30
[Java] String의 불변성은 SCP로부터  (0) 2023.01.27