본문 바로가기

Java

디폴트 메서드 정리

디폴트 메서드


1. 디폴트 메서드란?

자바 8부터 인터페이스에 메서드 구현을 포함시킬 수 있다.

이때 인터페이스에 구현하는 메서드를 디폴트 메서드라고 한다.

예시)

public interface Interface {
    default void defaultMethod(){
        System.out.println("디폴트메서드 입니다.");
    }
}
public class Client implements Interface {

}
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.defaultMethod();
    }

    //실행결과
    //디폴트메서드 입니다.
}

누군가는 디폴트 메서드를 포함한 인터페이스에 대해서 추상클래스와 무엇이 다른가? 혹은 왜 디폴트 메서드가 필요한가? 라는 의문이 생길 수 있다.

디폴트 메서드는 왜 필요할까?

다음과 같은 상황을 살펴보자


1. 대중적인 인터페이스 MyInterface 가 있다. (우리가 많이 사용하는 List 같은 인터페이스라고 생각하자)

2. 당신을 포함한 많은 사람들이 MyInterface 를 구현하는 클래스인 MyClass 를 사용하고 있다.

public interface MyInterface {
    ... 많은 유용한 기능들
}

public class MyClass implements MyInterface {
    ... 구현체 + 사용자가 커스텀한 기능들
}

3. 어느날 자바가 업데이트되면서 MyInterface 에 새로운 메서드가 추가된다.

public interface MyInterface {
    ... 많은 유용한 메서드

    void newMethod();
}

4. 당신은 MyClass에 newMethod() 를 구현하지 않았으므로 런타임에러 혹은 컴파일에러를 겪는다.


위와 같은 상황에서 디폴트 메서드를 사용한다면 문제를 해결할 수 있다.

MyInterface 라이브러리 개발자는 newMethod를 디폴트 메서드로 하여 인터페이스에서 매서드를 구현해주면 된다.

public interface MyInterface {
    default void newMethod(){
        실행할 로직작성
    };
}

*** 추상 클래스와 인터페이스의 차이 ***

추상클래스 vs 인터페이스
1. 추상클래스는 필드변수를 가질 수 있다.
2. 클래스는 하나의 추상클래스만 상속받을 수 있지만, 인터페이스는 여러개를 구현할 수 있다.

2. 디폴트 메서드 활용 패턴

2.1 선택형 메서드

인터페이스에는 포함되지만 실제로는 사용하지 않을 메서드가 있다면, 그 인터페이스를 구현하는 클래스에서는 사용여부에 상관없이 클래스를 필수적으로 구현주어야한다.

아마 이런 상황이라면 비어있는 메서드를 구현할 것이다.

public interface MyInterface {
    void usedFunction();

    void unUsedFunction();

    ...
}
public class MyClass implements MyInterface {

    @Override
    public void usedFunction() {
        ...
        ...
            로직을 구현한다
    }

    @Override
    public void unUsedFunction() {
        구현하지 않은채로 놔둔다(빈 구현)
    }
}

하지만 디폴트 메서드를 사용한다면 사용하지 않는 메서드는 굳이 빈 구현으로 구현할 필요가 없다.

실제로 자바8의 Iterator 인터페이스의 remove 가 디폴트 메서드로 정의되어 있다.

2.2 동작 다중 상속

위의 내용을 몇가지 다시 언급하면 다음과 같다.

1. 디폴트 메서드를 포함하는 인터페이스를 구현하는 클래스는 별도의 구현없이 그 디폴트 메서드를 사용할 수 있다
2. 인터페이스는 추상클래스와 달리 여러 개를 하나의 클래스에 구현시킬 수 있다.

위의 조건들을 이용한 것이 동작 다중 상속이다.

코드로 살펴보자.

public interface Walkable {
    default void walk(){
        System.out.println("걷기");
    }
}

public interface Runnable {
    default void run(){
        System.out.println("뛰기");
    }
}

public class Person implements Walkable,Runnable {

}

Person 클래스가 Walkable 과 Runnable 을 구현하면 그 안에 디폴트 메서드를 모두 사용가능하다.

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.run();
        person.walk();

        //출력결과
//        뛰기
//        걷기
    }
}

다중 동작 상속으로 발생할 수 있는 문제?

그렇다면 만약 같은 디폴트 메서드를 포함하는 두 인터페이스를 구현하는 상황에서 클래스는 어떤 메서드를 사용하게 될까?

각각의 상황에서 살펴보자.

상황1.

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

public interface B extends A {
    default void hello(){
        System.out.println("hello B");
    }
}

public class C implements B, A {
    public static void main(String... args){
        new C().hello();
    }
}
public class Main {
    public static void main(String[] args) {
        C c = new C();
        c.hello();
    }
}

과연 무엇이 출력될까?

같은 디폴트 메서드를 상속받을때 우선순위 규칙은 다음과 같다.

1. 클래스
2. 서브 인터페이스
3. 명시적으로 표시

위의 상황이라면 클래스에 정의한 메서드가 없고, B 가 A 를 상속받았으므로 서브 인터페이스가 B에 우선순위가 있다.

따라서 "hello B" 가 출력된다.

상황2.

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

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

public class C implements B, A {
    public static void main(String... args){
        new C().hello();
    }
}

이번에는 B 와 A 가 상속관계가 아니다.

hello 메서드를 구별할 기분이 없어졌으므로 자바 컴파일러는 에러를 발생시킨다.

에러를 해결하기 위해서는 어떤 메서드를 호출할지 명시적으로 선택해주어야 한다.

public class C implements B, A {
    public void hello(){
        B.super.hello();
    }
}

상황3.

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

public interface B extends A{
}

public interface C extends A {
}

public class D implements B, C {
    public static void main(String... args){
        new D().hello();
    }
}

claass D 는 서로 상속관계가 없는 B, C 를 구현하고 있다. 얼핏보면 상황2 와 비슷해 보이지만 그렇지 않다.

결국 B 와 C 둘다 A 의 hello()를 포함하고 있다.

따라서 "hello A"가 출력된다. (이를 '다이아몬드 문제'라고 부른다)

반응형