다형성_인터페이스

한라봉봉·2025년 7월 31일

JAVA

목록 보기
16/16

인터페이스

자바는 순수 추상 클래스를 더 편리하게 사용할 수 있는 인터페이스라는 기능을 제공한다.
인터페이스는 class가 아니라 interface 키워드를 사용하면 된다.

public interface InterfaceAnimal {
  void sound(); // public abstract 생략
  void move(); // public abstract 생략
}

인터페이스는 다음과 같은 특징을 가진다.

  • 인스턴스를 생성할 수 없다
  • 상속시 모든 메서드를 오버라이딩 해야 한다.
  • 주로 다형성을 위해 사용된다.
  • 인터페이스의 메서드는 모두 public, abstract 이다.
  • 메서드에 public abstract를 생략할 수 있다. 참고로 생략이 권장된다.
  • 인터페이스는 다중 구현(다중 상속)을 지원한다.

인터페이스와 멤버 변수

 public interface InterfaceAnimal {
 	public static final double MY_PI = 3.14;
 }

인터페이스에서 멤버 변수는 public, static, final이 모두 포함되었다고 간주된다. final은 변수의 값을 한번 설정하면 수정할 수 없다는 뜻이다.
자바에서 static final을 사용해 정적이면서 고칠 수 없는 변수를 상수라 하고, 관례상 상수는 대문자에 언더스코어(_)로 구분한다.

해당 키워드는 다음과 같이 생략할 수 있다.(생략 권장)

 public interface InterfaceAnimal {
 	double MY_PI = 3.14;
 }

예제

클래스 상속 관계는 UML에서 실선을 사용하지만, 인터페이스 구현(상속) 관계는 UML에서 점선을 사용한다.

public interface InterfaceAnimal {
  void sound(); // public abstract 생략
  void move(); // public abstract 생략
}
public class Dog implements InterfaceAnimal {
    @Override
 	public void sound() {
 		System.out.println("멍멍");
    }
    @Override
 	public void move() {
   		System.out.println("개 이동");
    }
}

인터페이스를 상속받을 때는 extends 대신에 implements 라는 구현이라는 키워드를 사용해야 한다. 인터페이스는 상속이라고 하지 않고 구현이라 한다.

 public class InterfaceMain {
   public static void main(String[] args) {
   	//인터페이스 인스턴스 생성 불가
  	//InterfaceAnimal interfaceMain1 = new InterfaceAnimal();
     Dog dog = new Dog();
     soundAnimal(dog);
   }
   //동물이 추가 되어도 변하지 않는 코드
  	private static void soundAnimal(InterfaceAnimal animal) {
      System.out.println("동물 소리 테스트 시작");
      animal.sound();
      System.out.println("동물 소리 테스트 종료");
   }
 }

동물 소리 테스트 시작
멍멍
동물 소리 테스트 종료

클래스, 추상 클래스, 인터페이스는 프로그램 코드, 메모리 구조상 모두 똑같다.

  • 모두 자바에서는 .class로 다루어진다. 인터페이스를 작성할 때도 .java에 인터페이스를 정의한다.
  • 인터페이스는 순수 추상클래스와 비슷하다고 생각하면 된다.

상속 vs 구현

부모 클래스의 기능을 자식 클래스가 상속 받을 때, 클래스는 상속 받는다고 표현하지만, 부모 인터페이스의 기능을 자식이 상속 받을 때는 인터페이스를 구현한다고 표현한다.

  • 상속은 이름 그대로 부모의 기능을 물려 받는 것이 목적
  • 구현이라고 하는 것은 물려받을 수 있는 기능이 없고, 오히려 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩 해서 기능을 구현해야 하기 때문

인터페이스를 사용해야 하는 이유

좋은 프로그램은 제약이 있는 프로그램이다.

모든 메서드가 추상 메서드인 경우 순수 추상 클래스를 만들어도 되는데 왜 인터페이스를 쓸까?

  • 제약: 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다. 인터페이스는 추상메서드가 아니면 제한되므로 이런 문제를 원천 차단한다.
  • 다중 구현: 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러명 두는 다중구현(다중 상속)이 가능하다.

참고

자바8에 등장한 default 메서드를 사용하면 인터페이스도 메서드를 구현할 수 있다. 하지만 이것은 예외적으로 아주 특별한 경우에만 사용해야 한다. 자바9에서 등장한 인터페이스의 private메서드도 마찬가지이다.

인터페이스 다중 구현

자바가 다중 상속을 지원하지 않는 이유

자바는 다중 상속을 지원하지 않아 extends 대상은 하나만 선택할 수 있다.

위와같이 다중 상속을 지원하면 AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move()를 사용해야 할지 애매한 문제가 발생한다. 이를 다이아몬드 문제라 한다. 그리고 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해 질수 있다. 이런 문제때문에 자바는 클래스의 다중 상속을 허용하지 않고 대신에 인터페이스의 다중 구현을 허용한다.

자바가 인터페이스의 다중 구현은 허용한 이유

InterfaceA, InterfaceB는 모두 methodCommon()을 가지고 있지만 인터페이스 자신이 구현을 가지지 않는다.
Child는 두 부모중에 어떤 한 부모의 methodCommon()을 선택하는 것이 아니라 그냥 인터페이스를 구현한 Child에 있는 methodCommon()이 사용된다.
따라서 다이아몬드 문제가 발생하지 않으므로 인터페이스의 경우 다중 구현을 허용한다.

예제

 public interface InterfaceA {
   void methodA();
   void methodCommon();
 }
 public interface InterfaceB {
   void methodB();
   void methodCommon();
 }
//인터페이스 2개 구현
 public class Child implements InterfaceA, InterfaceB {
    @Override
    public void methodA() {
     	System.out.println("Child.methodA");
    }
    @Override
    public void methodB() {
     	System.out.println("Child.methodB");
    }
    @Override
   public void methodCommon() {
     	System.out.println("Child.methodCommon");
   	}
 }
//인터페이스 다중 구현
public class DiamondMain {
  public static void main(String[] args) {
 	InterfaceA a = new Child();
    a.methodA();
    a.methodCommon(); //Child타입의 인스턴스에서 메소드 호출
    
 	InterfaceB b = new Child();
    b.methodB();
    b.methodCommon(); //Child타입의 인스턴스에서 메소드 호출
    
    Child c = new Child();
    c.methodCommon(); //Child타입의 인스턴스에서 메소드 호출
  }
 }


1. a.methodCommon()을 호출하면 먼저 X001 Child 인스턴스를 찾는다.
2. 변수 a 가 Interface 타입이므로 해당타입에서 methodCommon()을 찾는다.
3. methodCommon()은 하위 타입인 Child에서 오버라이딩 되어 있다. 따라서 Child의 methodCommon()이 호출된다.

profile
백엔드 개발공부 로그를 기록합니다

0개의 댓글