Java

[Java] 인터페이스 default method, static method

Tommy__Kim 2024. 1. 18. 08:07

들어가며

자바에서는 인터페이스를 사용해 인터페이스를 구현하는 클래스에서 추상 메서드를 구현해야 합니다. 

public interface A {
    void doSomething();
}

A 인터페이스에는 doSomething 이라는 추상 메서드가 존재합니다. 

public class AImpl implements A{
    @Override
    public void doSomething() {
        System.out.println("this is A");
    }
}

A인터페이스를 구현한 AImpl의 경우 doSomething 메서드를 override 하여 재정의 해 주어야 합니다. 

 

만약 인터페이스에 추상 메소드가 추가되는 경우에는 이를 구현하는 클래스에서 다시 재정의 해 주어야 합니다. 

public interface A {
    void doSomething();

    void doSomething2();
}

A 인터페이스에 doSomething2이라는 추상 메서드가 추가되었습니다. 

 

public class AImpl implements A{
    @Override
    public void doSomething() {
        System.out.println("this is A");
    }

    @Override
    public void doSomething2() {
        System.out.println("Hello");
    }
}

A 인터페이스를 구현한 AImpl의 경우 추가된 메서드인 doSomething2 메서드를 재정의 해 주어야 합니다. 

 

만약 이러한 인터페이스가 API로 제공되어 수많은 곳에서 사용된다면 어떻게 될까요?

이를 구현한 다수의 클래스에서 재정의 해 주어야 하는 불편한 일이 생깁니다.

 

Java8에서는 이러한 문제점을 해결하고자 Java8부터 Interface에 default method, static method를 제공하기 시작합니다. 

 

default method 

public interface A {
	
    // .. 
    
    default void defaultMethod() {
        System.out.println("this is defaultMethod");
    }
}

default 메소드는 위와 같이 인터페이스에 추상메소드가 아닌 메소드 구현체를 제공하는 방식입니다. 

default 메소드는 여러가지 특징을 지니는데 대표적인 특징은 다음과 같습니다.

  • 해당 인터페이스를 구현한 클래스를 깨트리지 않으며 새 기능 추가가 가능하다. 
  • Object가 제공하는 기능 (equals, hashCode, toString.. )은 default 메소드로 제공할 수 없다.
  • 본인이 수정 가능한 인터페이스에만 default 메소드를 제공할 수 있다.
  • 인터페이스를 상속받는 인터페이스에서 다시 추상메소드로 변경 할 수 있다. 
  • 인터페이스 구현체에서 재정의가 가능하다. 

default 메소드의 경우 구현체가 모르게 추가된 기능으로 리스크가 존재합니다. 

예를 들어 default 메소드에서 기존에는 값들을 더해주는 기능을 제공하다가 API 변경 후에는 값들을 곱해주는 기능을 제공한다면, 런타임 예외가 발생할 수 있습니다.

따라서 default 메소드의 경우 javadoc의 @implSpec을 사용해 명세화 해주는 것이 권장됩니다. 

public interface A {
	
    // .. 

    /**
     * @implSpec 현재는 초기 버전으로 단순히 System.out.println() 기능만 제공합니다.
     */
    default void defaultMethod() {
        System.out.println("this is defaultMethod");
    }
    
}

default method 활용 패턴 

default 메소드의 경우 주로 선택형 메소드 그리고 다중 상속 형태로 사용합니다.

 

선택형 메소드 

public interface A {

	// ..
    
    default void defaultMethod() {
        System.out.println("this is defaultMethod");
    }
    
}

이러한 인터페이스가 존재할 때 A를 구현한 구현체들 중 defaultMethod를 지원하지 않는 기능들도 존재할 수 있습니다. 

이럴 때, 만약 defaultMethod를 호출한다면 예외가 발생하게끔 구현해 해당 문제를 해결할 수 있습니다.

 

public class AImpl implements A{

	// .. 

    @Override
    public void defaultMethod() throws OperationNotSupportedException {
        throw new OperationNotSupportedException("This method is not allowed in AImpl");
    }
}

 

다중 상속

다중 상속 방식을 통해 default 메소드를 손쉽게 사용할 수 있습니다.

public interface Jumpable {

    int getY();

    default int jump(int height) {
        return getY() + height;
    }
}

public interface Movable {

    int getX();
    default int moveHorizontal(int distance) {
        return getX() + distance;
    }
}

public class Person implements Movable, Jumpable{
    private int x;
    private int y;
    @Override
    public int getY() {
        return y;
    }

    @Override
    public int getX() {
        return x;
    }
}

public class App {
    public static void main(String[] args) {
        Person person = new Person();
        person.jump(10);    // Jumpable 의 default method
        person.moveHorizontal(20); //Movable의 default method
    }
}

Jumpable, Movable은 각 추상 메소드 하나, default 메소드 하나를 가지고 있습니다. 

Person의 경우 두가지 인터페이스를 구현하고 있습니다. Person은 Movable, Jumpable의 default 메소드를 사용할 수 있습니다.

 

Diamond 문제 

극히 드물지만 만약 구현하고자 하는 인터페이스들 중 같은 시그니처를 갖는 default 메소드가 존재하는 경우 어떻게 될까요? 

public interface A {
    void a();

    default void defaultMethod(){
        System.out.println("this is defaultMethod");
    }

}

public interface B {
    void b();

    default void defaultMethod() {
        System.out.println("This is B");
    }
}

public class AB implements A, B{
    @Override
    public void a() {
        System.out.println("AB.a");
    }

    @Override
    public void b() {
        System.out.println("AB.b");
    }
}

현재 인터페이스 A, B 모두 동일한 시그니처를 가지고 있는 defaultMethod가 존재합니다. 

AB의 경우 어떠한 default 메소드를 사용해야하는 지 알 수 없기 때문에 컴파일 에러가 발생합니다. 

이러한 경우 AB 에서 재정의 해주어야 합니다. 

public class AB implements A, B{
    @Override
    public void a() {
        System.out.println("AB.a");
    }

    @Override
    public void b() {
        System.out.println("AB.b");
    }

    @Override
    public void defaultMethod() {
        System.out.println("AB.defaultMethod");
    }
}

 

static method

static 메소드의 경우 해당 타입 관련 헬퍼 혹은 유틸리티 메소드를 제공할 때 static 메소드를 통해서 제공할 수 있습니다.

public interface A {
 
    // .. 
    
    static void getInformation() {
        System.out.println("A Interface");
    }

}


public class App {
    public static void main(String[] args) {
        A.getInformation();
    }
}