인터페이스 default/static method

17913 단어 JavaJava

자바 8버전 이후부터 인터페이스에 기본 메소드(default method)와 스태틱 메소드(static method)를 사용할 수 있다.

기본 메소드(Default Method)

여러 구현체들이 한 인터페이스를 구현하고 있을 때, 인터페이스에 어떤 기능을 추가하기 위해서는 그것을 구현한 구현체들 모두에서 기능에 대해 정의해주어야 한다.

이런 불편함 때문에 자바 8 버전 이후부터 인터페이스 내부에 default method를 구현할 수 있게 됐다.
(때문에 함수형 인터페이스(Functional Interface)에 여러 개의 default method와 static method가 있어도 추상 메소드가 하나인 경우 함수형 인터페이스로 본다. 즉, 추상 메서드의 개수가 기준이 된다.)

기본 메소드란 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법이다.

  • 장점
    • 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능 추가 가능
  • 단점
    • 구현체가 모르게 추가된 기능이기 때문에 리스크가 따른다.
      • 구현체에 따라 런타임 에러가 발생할 수 있다.
      • 따라서 반드시 문서화해야 한다. (@implSpec)
  • 특징
    • Object에서 제공하는 메소드(equals, toString)과 같은 메소드는 기본 메소드로 제공 불가
      • 추상 메소드로 선언은 가능
      • 구현체가 재정의해야 한다.
    • 구현체를 정의해야 하기 때문에 수정할 수 있는 인터페이스에만 제공 가능 (외부 라이브러리에 있는 인터페이스에서는 불가)
    • 기본 메소드가 정의되어 있는 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경 가능
    • 구현체에서 기본 메소드 재정의 가능
    • 여러 인터페이스에 같은 이름의 기본 메소드가 있고, 이 인터페이스들을 구현할 때는 무조건 재정의 해줘야 한다.

구현체가 모르게 추가된 기능이기 때문에 리스크가 따른다.

public interface Foo {
    String getName();
    
    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대분자로 바꿔 출력한다.
     */
    default void printNameUpperCase() {
        System.out.println(getName().toUpperCase());
    }
}
public class DefaultFoo implements Foo {
    String name;
    
    @Override
    public String getName() {return null;}
}

위의 경우 printNameUpperCase()를 다른 구현체에서는 문제가 없더라도 DefaultFoo에서는 nullPointException이 발생할 것이다.

상속받는 인터페이스에서 다시 추상 메소드로 변경 가능

public interface Foo {
    String getName();
    
    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대분자로 바꿔 출력한다.
     */
    default void printNameUpperCase() {
        System.out.println(getName().toUpperCase());
    }
}
public interface Foo2 extends Foo {
    void printNameUpperCase();
}

구현체에서 기본 메소드 재정의 가능

public interface Foo {
    String getName();
    
    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대분자로 바꿔 출력한다.
     */
    default void printNameUpperCase() {
        System.out.println(getName().toUpperCase());
    }
}
public class DefaultFoo implements Foo {
    String name;
    
    @Override
    public String getName() {return null;}
    
    @Override
    public void printNameUpperCase() {
        System.out.println("default 메소드도 재정의 가능");
    }
}

여러 인터페이스, 같은 이름의 기본 메소드

public interface Foo1 {
    String getName();
    
    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대분자로 바꿔 출력한다.
     */
    default void printName() {
        System.out.println(getName().toUpperCase());
    }
}
public interface Foo2 {
    String getName();
    
    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대분자로 바꿔 출력한다.
     */
    default void printName() {
        System.out.println(getName().toLowerCase());
    }
}
public class DefaultFoo implements Foo1, Foo2 {
    String name;
    
    public DefaultFoo(String name) {this.name = name;}
    
    @Override
    public String getName() {return name;}
    
    @Override
    public void printName() {
        System.out.println("Foo1, Foo2에 같은 이름의 기본 메소드는 재정의");
    }
}

만약 여러 인터페이스에 같은 이름의 기본 메소드가 있고 한 클래스에서 이 인터페이스들을 구현하는데, 이를 재정의 해주지 않을 때에는 모호하기 때문에 컴파일 에러가 발생한다.

static 메소드

인터페이스에서 기본 메소드 이외에 static 메소드도 정의할 수 있게 되었다.

public interface Foo {

    void printName();

    static void printClassName() {
        System.out.println("Foo");
    }
}
public class DefaultFoo implements Foo {

    String name;

    public DefaultFoo(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        Foo.printClassName(); // static 메소드 사용 가능
        
        return this.name;
    }
}

추상 클래스 VS 인터페이스 (java 8)

Java 8 이전에서 추상 클래스와 인터페이스의 차이점 중 가장 큰 차이점은 구현부를 가질수 있는지 여부였다. 하지만 Java 8로 넘어오면서 이 차이점이 사라져 비슷해졌다고 생각할 수 있다. 때문에 둘 간의 차이점을 명확히 할 필요가 있다.

추상클래스

  • 관련성이 높은 클래스 간에 코드를 공유하고 싶을 때
  • 클래스들이 공통으로 가지는 메소드와 필드가 많거나, 접근제어자의 사용이 필요한 경우
  • non-static, non-final 필드 선언이 필요할 경우
  • 생성자가 필요할 경우

인터페이스

  • 서로 관련성이 없는 클래스들이 인터페이스를 구현하게 되는 경우
    • Comparable의 경우 여러 클래스에서 구현되는데, 그 클래스들 간의 관련성이 없는 경우가 많음
  • 다중 상속을 허용하고 싶을 때

좋은 웹페이지 즐겨찾기