Backend/☕️ Java

[Java] 자바의 다중상속 (feat. 인터페이스와 추상클래스)

Hugehoo 2021. 11. 16. 19:49

알고 시작해야 할 내용

1. 자바에서 클래스의 다중 상속은 불가능하다.

2. 하지만 인터페이스의 다중 상속은 가능하다. 

 

why?

 

인터페이스와 추상클래스를 공부하던 중, 자바의 다중상속 이란 개념을 알게 됐다. 어떤 이유에서 인터페이스는 다중 상속이 가능하고, 클래스(+추상 클래스) 는 불가능한지 이유를 풀어보겠다.

 

인터페이스와 추상 클래스를 사용하는 이유

  • 설계시 인터페이스와 추상클래스를  미리 선언해두면 개발시 기능 구현에만 집중할 수 있다. 즉 개발자는 비즈니스 로직에만 집중할 수 있게 된다.
  • 공통의 인터페이스와 추상 클래스를 선언해두면, 선언과 구현을 구분할 수 있다.

 

그럼 인터페이스만 있으면 되지, 추상 클래스는 왜 필요하지?

  • 인터페이스를 선언하다보니 어떤 메소드는 미리 선언 해놓을 필요가 있다.
  • 그럼 직접 클래스를 만들면 되지 않나 싶지만 해당 클래스까지 만들기엔 애매할 경우에, 
  • 아주 공통적인 기능을 미리 구현해두면 많은 도움이 된다. 미리 구현한다는 의미는 추상 메서드가 아닌 일반 메서드를 생성한다는 의미다.
  • 이런 경우에 추상 클래스를 사용한다. 미리 구현해두면 좋은 기능은 미리 메서드로 생성해두고, 그렇지 않은 메서드는 추상 메서드로 남겨둔다. (인터페이스를 보완한 클래스라 생각해도 무방하다)

 

인터페이스 특징

  • Interface는 모든 메서드가 추상 메서드인 경우를 뜻한다.
  • 즉, ex) public interface A{ ... } 내의 모든 메서드는 선언만 된 상태일 뿐, 어떠한 기능을 할지 '정의' 되지 않은 상태다.
  • 이렇게 정의 되지 않은 메서드를 '추상 메서드'라고 부른다.
  • 인터페이스 내의 모든 메서드는 추상 메서드로 간주되기에 abstract를 (굳이) 적지 않는다 (어차피 모두 추상 메서드이니)
  • 때문에 인터페이스는 추상 클래스보다 한 단계 더 추상화 된 클래스로 간주된다.
  • 추상 클래스와 달리 다중 상속이 가능하다. 🔽
public class Toy implements ToyRobot, ToyAirplane{ // 이렇게 여러 인터페이스를 동시에 구현하는 것을 다중 상속이라 한다.
	
    	...
    
	@Override
	public void fly(String s, String b) {
		System.out.println("===air===");
		System.out.println(s+ " can " +b);
	}

	@Override
	public void run() {
		System.out.println("===robot===");
		System.out.println("run");		
	}
}

 

 

추상 클래스의 특징

  • 최소 하나 이상의 추상 메서드를 가지는 클래스를 의미한다.
  • 그 말은 추상 메서드가 아닌, 미리 구현된 메서드를 가질 수도 있다는 의미다.
  • 즉 인터페이스와 추상 클래스의 가장 구분되는 특징은, 미리 구현된 메서드를 가지고 있느냐의 차이다.
  • 인터페이스와 마찬가지로, 직접 객체를 구현할 수 없다(=new 연산자를 사용할 수 없다는 의미). 아직 정의되지 않은 메서드(=추상 메서드)가 존재하기 때문이다.

 

 

OOP 에서는 다형성을 중요시하지만, 자바는 클래스의 다중 상속을 막았기 때문에 자유로운 다형성 부여가 어렵다. 

대신, 인터페이스라는 (제한된) 다중 상속을 허용했다. 인터페이스 내부에서 선언된 메서드는 추상 메서드이기 때문에, 상속받는 클래스에선 무조건 해당 메서드를 오버라이드 해야한다.

 

 

이제  처음 질문으로 돌아가보자. 
왜 인터페이스의 다중 상속은 허용되고,  클래스는 안되는 걸까?

만약 자식클래스가 둘 이상의 클래스로부터 다중 상속을 받는다고 가정하자.

ParentA 부모 클래스에 love() 라는 메서드가 이미 정의되어있고, 마찬가지로 ParentB 라는 부모 클래스에도 love() 메서드가 존재한다. love() 메서드가 동일한 시그니처를 가진다면,  자식클래스는 둘 중 어느것을 상속받아야 할까?

(동일 시그니처 : 메서드의 접근 제어자, 리턴 타입, 메서드 명, 매개변수가 모두 동일함을 의미. 메서드 구현부는 같을수도, 다를 수도 있다.)

 

자식 클래스 입장에서는, love() 메서드를 상속받을 때, 어느 부모의 메서드를 상속받아야할 지 파악할 수 없게 된다. 이미 구현이 완료된 동일 시그니처의 메서드를 상속받는 일은 결국 불가능하다. 

 

반면 인터페이스는 다중 상속이 가능하지만, 위의 문제를 피해 갈 수 있다. 왜냐하면 메서드가 정의되지 않았기 때문이다.
즉 선언된 형태의 메서드만 가지는 인터페이스는, 동일 시그니처의 메서드를 얼마든 상속받아도 아무런 문제가 발생하지 않는다.

해당 메서드는 아직 구현되지 않아 자식 클래스에서 새롭게 정의해야 하기 때문이다. 

 

인터페이스의 다중 상속

public interface Father{
   void love();
}

public interface Mother{
   void love();
}
public class Child implements Father, Mother {
   @Override
   void love(){
      System.out.println("L.O.V.E");
   }
  
   public static void main(){
      Child child = new Child();
      child.love();  // "L.O.V.E"
   }
}

 

 

마무리

  • 인터페이스는 구현된 메서드가 없기 때문에, 다중상속이 가능하다.
  • 하지만, 클래스나 추상 클래스는 이미 구현된 메서드가 존재하기 때문에 다중 상속이 불가능하다.

사실 다중상속이 되느냐 안되느냐가 그리 큰 문제인지는 잘 모르겠다. 하지만 어떤 이유로 다중상속이 가능하거나, 불가능한지 파악하는 것은 꽤 의미있는 공부라 느꼈다.

 

 

ref

https://siyoon210.tistory.com/125

https://youngjinmo.github.io/2021/03/diamond-problem/

https://chae96.tistory.com/2