Java - Interface의 Default Methods 이해하기

Java8에서 인터페이스에 디폴트 메소드(Default methods)라는 것이 추가되었습니다. 인터페이스는 메소드 정의만 할 수 있고 구현은 할 수 없었습니다만, Java8부터 디폴트 메소드라는 개념이 생겨 구현 내용도 인터페이스에 포함시킬 수 있었습니다.

Default methods에 대해서 알아보겠습니다.

1. Default methods를 정의하는 방법

다음은 일반적으로 인터페이스를 구현한 코드입니다.

public interface Vehicle {
    public void doSomething(int n);
}

디폴트 메소드를 사용하면 구현내용도 인터페이스에 포함시킬 수 있습니다. 아래 코드처럼 메소드 이름 앞에 default 키워드를 입력하고 구현 내용을 추가하시면 됩니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

디폴트 메소드가 구현된 인터페이스도 상속받을 수 있습니다. 다음 코드는 VehicleChild가 Vehicle를 상속받는 코드입니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

public interface VehicleChild extends Vehicle {
}

Vehicle는 VehicleChild의 디폴트 메소드 구현도 함께 상속받게 됩니다.

2. 클래스가 인터페이스를 implements할 때

클래스가 디폴트 메소드가 정의된 인터페이스를 implements하면 어떻게 될까요? 예상하시는 것처럼 인터페이스의 디폴트 메소드가 클래스에 자동으로 구현이 됩니다.

다음은 디폴트 메소드가 정의된 Vehicle 인터페이스를 Car 클래스가 implements하는 코드입니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

public static class Car implements Vehicle {
}

public static void main(String args[]) {
    Car car = new Car();
    car.doSomething(10);
}
// 실행 결과
// doSomething(Vehicle)

코드 실행 결과를 보시면 implements했을 때 디폴트 메소드에 구현된 것도 함께 클래스에 적용된 다는 것을 알 수 있습니다.

3. (다중상속개념) 클래스가 두개의 인터페이스를 implements 했을 때

어떤 클래스가 두개의 인터페이스를 implements하면 어떻게 될까요? 그냥 인터페이스도 아니고 동일한 메소드 이름(Signature)으로 디폴트 메소드를 구현한 인터페이스입니다.

개념적으로 다중상속이기 때문에 컴파일러는 어떤 인터페이스의 메소드를 상속받아야 할 지 헷갈릴 수 있습니다. 결과를 먼저 말씀드리면 컴파일 에러가 발생합니다.

다음 코드는 Car 클래스가 동일한 메소드를 디폴트 메소드로 구현한 Vehicle과 Movable을 implements했습니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

public interface Movable {
    public default void doSomething(int n) {
        System.out.println("doSomething(Movable)");
    }
}

public class Car implements Vehicle, Movable {
}

이 코드는 다음과 같은 컴파일 에러가 발생합니다.

Error:(33, 16) java: class Car inherits unrelated defaults for doSomething(int) from types Vehicle and Movable

두개의 메소드 이름이 동일하기 때문에 어떤 것을 구현할지 몰라 에러가 발생했습니다. 컴파일 에러를 해결하려면 Car 클래스에서 디폴트 메소드를 Override 해줘야 합니다.

저는 아래처럼 Vehicle과 Movable에 구현된 것을 모두 호출하고 Car의 로그도 출력하도록 오버라이드했습니다. 실행결과를 보시면 3개의 로그가 모두 출력되었습니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

public interface Movable {
    public default void doSomething(int n) {
        System.out.println("doSomething(Movable)");
    }
}

public static class Car implements Vehicle, Movable {
    @Override
    public void doSomething(int n) {
        Vehicle.super.doSomething(n);
        Movable.super.doSomething(n);
        System.out.println("doSomething(Car)");
    }
}

public static void main(String args[]) {
    Car car = new Car();
    car.doSomething(10);
}

// 실행 결과
// doSomething(Vehicle)
// doSomething(Movable)
// doSomething(Car)

4. (다중상속개념) 클래스가 extends와 implements 했을 때

어떤 클래스가 디폴트 메소드를 구현한 클래스를 상속받고(extends), 디폴트 메소드를 구현한 다른 인터페이스를 구현(implements)할 때가 있습니다.

위의 인터페이스 다중 상속과 비슷한 케이스입니다. 하지만 자바는 다중상속을 지원하지 않고 extends와 implements를 다르게 처리합니다. 결과를 먼저 말씀드리면 컴파일러는 충돌이 발생할 때 extends한 클래스의 우선순위가 더 높아, 이 클래스의 디폴트 메소드를 상속받습니다.

예를 들면, 다음 코드는 컴파일 에러 없이 빌드가 됩니다. 동일한 메소드 signature로 충돌이 발생할 때 컴파일러는 상속받은 클래스의 디폴드 메소드를 우선시하기 때문입니다. 그래서 결과를 보면 MovableCar는 Car의 디폴트 메소드를 호출하는 것을 알 수 있습니다.

public interface Vehicle {
    public default void doSomething(int n) {
        System.out.println("doSomething(Vehicle)");
    }
}

public interface Movable {
    public default void doSomething(int n) {
        System.out.println("doSomething(Movable)");
    }
}

public static class Car implements Vehicle {
    @Override
    public void doSomething(int n) {
        System.out.println("doSomething(Car)");
    }
}

public static class MovableCar extends Car implements Movable {
}

public static void main(String args[]) {
    MovableCar car = new MovableCar();
    car.doSomething(10);
}

// 실행 결과
// doSomething(Car)

5. 추상 클래스 vs 인터페이스

디폴트 메소드의 도움으로 인터페이스는 추상 클래스(abstract)와 차이가 없어보입니다. 하지만 실제로 다음과 같은 차이가 있습니다.

  • 추상 클래스, 인터페이스 둘 다 객체로 만들 수 없다는 공통점이 있습니다. extends하거나 implements해야 합니다.
  • 추상 클래스에는 public, protected, private 메소드를 가질 수 있습니다. 반면에 인터페이스는 public만 허용됩니다.
  • 추상 클래스에는 멤버변수 선언이 가능하지만 인터페이스는 public static 변수만 선언이 가능합니다.
  • 인터페이스는 implements 키워드로 여러 인터페이스를 구현할 수 있습니다. 반면에 추상클래스는 extends 키워드로 1개의 클래스만 상속받을 수 있습니다.
Loading script...

Related Posts

codechachaCopyright ©2019 codechacha