본문 바로가기

JAVA

[Java] 클래스는 왜 하나만 상속 받을 수 있을까?

자바를 공부하다 보면 금방 마주치는 제약 중 하나가 있습니다.
“자바는 클래스의 다중 상속을 지원하지 않는다.”
실제 얼마전에 면접 질문으로 받았던 질문이었습니다. 나름 잘 대답했다고 생각했는데 면접관의 질문이 "그것밖에 없나요?" 였다. 모르는 이유가 있는걸까? 생각이 들어 공부한 내용을 정리해보았습니다.

 

클래스와 인터페이스의 차이점

인터페이스는 클래스와 다르게 다중 상속을 지원합니다. 이 차이를 이해하기 위해 클래스와 인터페이스의 차이부터 설명하겠습니다.

 

1. 관점: 구현 VS 설계

클래스는 구현 중심입니다. 필드와 메서드를 함께 정의하며 객체를 생성할 수 있는 구조를 정해줍니다.

이에 반해 인터페이스는 설계 중심입니다. 인터페이스에서는 단지 설계를 진행하고 상속받은 클래스에서 인터페이스에서 설계한 내용을 구체화합니다.

 

2. 인스턴스 생성

클래스는 객체를 생성할 수 있는 구조를 정해주기 때문에 당연하게 인스턴스 즉 객체를 생성할 수 있습니다.

이에 비해 인터페이스는 단지 설계만 제공하고 실제 구체화는 상속된 클래스에서 진행하기 때문에 객체를 생성할 수 없습니다.

class Car {
    public void drive() {
        System.out.println("주행 중...");
    }
}

interface Drivable {
    void drive();
}

Car car = new Car();            // 가능
Drivable d = new Drivable();   // 컴파일 에러

 

 

3. 클래스는 상태 변수를 가질 수 있다.

클래스 객체를 생성할 수 있습니다. 이에 따라 객체마다 바뀔 수 있는 상태 변수(멤버 변수)를 선언하고 값을 변경할 수 있습니다.

인터페이스 자체로 객체를 생성할 수 없고 클래스로 구체화하기 때문에 인터페이스에는 상태 변수가 있을 수 없습니다. 인터페이스에 정의되는 필드는 암묵적으로 변경할 수 없는 상수 취급 받습니다.

 

class Person {
    private String name; // 변경 가능 상태 변수
}

interface Livable {
    int MAX_VALUE = 100; // public static final 생략
}

 

그러면 왜 클래스는 하나만 상속 받을 수 있을까?

그렇다면 왜 클래스는 하나의 상속만 받을 수 있을까? 다중 상속을 허용할 경우 부모 클래스들에서 동일한 이름의 메서드나 필드가 존재할 때 어떤 것을 상속받을지 애매해지는 문제가 발생합니다. 이를 다이아몬드 문제라고 합니다.

 

class A {
	int number;
    
    void count() {
    	number++;
    }
}

class B {
	int number;
    
    void count() {
    	number--;
    }
}

class C extends A, B {
	number??
    
    count??
}

앞에서 설명했다시피 클래스는 필드와 메소드 구현체가 있습니다. 이 때 부모 클래스(C) 입장에서 A,B 중 어떤 항목을 받을 지 애매해지는 경우가 존재합니다.

 

이에 비해 인터페이스는 메소드의 설계 부분만 두기 때문에 겹치는 메소드가 존재한다고 해서 충돌이 일어나지 않습니다.

interface A {
    void doWork();
}

interface B {
    void doWork();
}

class C implements A, B {
    @Override
    public void doWork() {
        System.out.println("구현 클래스에서 직접 정의한 로직");
    }
}

 

 

인터페이스의 상수가 겹치면?

인터페이스는 오직 상수만 정의할 수 있습니다. 두 인터페이스에 동일한 이름의 상수가 존재할 경우, 컴파일러는 어떤 상수를 의미하는지 명확하지 않다며 에러를 발생시킵니다.

 

interface A {
    int VALUE = 10;
}

interface B {
    int VALUE = 20;
}

class C implements A, B {
    public void printValue() {
        // System.out.println(VALUE); // 컴파일 에러 발생
        System.out.println(A.VALUE); // 명시적으로 지정해야 함
    }
}

 

 

자바 8 이후 디폴드 메소드는?

Java 8부터 인터페이스는 default 메서드를 통해 메소드의 공통적인 구현도 포함할 수 있습니다. 이로 인해 다시 충돌의 가능성이 생겼지만, 자바는 명시적 해결 방법을 제공합니다. 이는 실제 어떤 구현 부분을 상속받을건지 요구합니다.

interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

interface B {
    default void hello() {
        System.out.println("Hello from B");
    }
}

class C implements A, B {

	//해당 부분이 존재하지 않으면 모호성으로 인한 컴파일 에러 발생
    @Override
    public void hello() {
        A.super.hello();
    }
}

 

그렇다면 이런 의문이 듭니다.

 

"그럼 클래스도 컴파일러가 의도적으로 명시하게 하면 될 거 아니야?"

 

맞습니다. 하지만 결론부터 말하면 자바에서는 의도적으로 그렇게 되지 않게 선언했습니다. 개발자 입장에서는 클래스의 장점이 사라지면 복잡성이 증가해하게 되기 때문입니다.

 

class A { int number = 1; }
class B { int number = 2; }
class C extends A, B { 
	number //number를 수정하는거면 뭘 수정해야해??
}

class C extends A, B {
    @Override
    void count() {
        A.super.count();  // A의 count 사용
        B.super.count();  // B의 count 사용
        
        //둘 중에 하나를 선택하는 것이 아닌 둘다 진행해야 하기 때문
    }
}

 

인터페이스와 다르게 부모 클래스도 하나의 객체이고 자식 클래스 입장에서는 부모 클래스의 모든 부분을 수행해야 하는 의무가 있습니다. 위와 같이 필드는 모호해지고 메소드는 상속 받은 모든 메소드를 진행하게 유도해야 하는 방식은 복잡성이 증가합니다. 이는 유지소수와 코드 가독성을 심각하게 줄어들게 됩니다. 이를 방지하기 위해 자바는 클래스의 다중 상속을 의도적으로 막습니다.

 

 

클래스와 인터페이스는 자바 언어를 개발할 당시에 객체지향 프로그래밍을 위한 중요한 항목입니다. 제약을 푼다면 더 자유로운 코드 작성이 가능하겠지만 이러한 제약은 오히려 자바 개발 시 방향성과 일관된 구조를 제공하는 '설계 가이드라인'이 된다고 생각합니다. 이 때문에 클래스는 다중 상속이 불가능합니다.

'JAVA' 카테고리의 다른 글

익명 클래스와 익명객체(Anonymous Object)  (1) 2025.11.04
[문법] int 배열의 stream  (1) 2024.06.10
[JAVA] Steam의 특징과 컬랙션과 차이  (0) 2024.03.12