컴퓨터 언어/Java

Java 인터페이스의 디폴트(default) 메소드와 정적(static) 메소드

redsiwon 2023. 4. 26. 21:14

디폴트(default) 메소드

Java 8부터 등장하여 인터페이스에서만 선언할 수 있으며, 기존에 인터페이스의 모든 메소드는 추상 메소드로서 구현할 수가 없었는데, 구현할 수 있는(그리고 반드시 구현해야 하는) 메소드입니다. 접근제어자 default와는 별개이며, 접근제어자는 기존 인터페이스의 메소드와 마찬가지로 public만 가능합니다.

 

디폴트 메소드는 하위 호환성을 해치지 않고 설계를 확장할 수 있도록 도와줍니다. 이런 이유로 다른 말로는 확장(extension) 메소드라고도 불립니다. (물론 디폴트 메소드는 인터페이스 내에서 구현해야 하므로 이를 구현한 클래스의 필드는 사용하지 못한다는 단점이 존재합니다.)

 

예를 들어봅시다. move()라는 동작을 갖는 Animal이라는 인터페이스가 존재합니다. Animal을 구현한 클래스들은 move()만을 재정의하면 됩니다.

public interface Animal {
void move();
}
public class Ant implements Animal {
@Override
public void move() {
System.out.println("ant move");
}
}

위 상황에서 Animal을 Ant는 move()를 재정의했습니다.

그런데 만약 Animal에 breathe()라는 동작이 추가되면 어떻게 될까요? 

...

 

 

아래와 같이 에러가 발생합니다.

breathe()를 구현하지 않아서 에러 발생 -> 확장은 하위 호환성을 고려해야 한다.

만약 Animal을 구현한 클래스가 100개면, 100개의 클래스 전부를 수정해야만 빌드를 할 수 있습니다. 이 문제를 해결하는 방법이 바로 디폴트 메소드 입니다!

 

디폴트 메소드의 활용 방법

위와 같은 불상사를 막기 위한 절차는 다음과 같습니다.

 

1. 선제적으로 breathe()를 default 메소드로 선언하여 하위호환성을 해치지 않고 확장합니다.

public interface Animal {
void move();
default void breathe() {
System.out.println("animal breathe");
}
}
Animal ant = new Ant();
ant.move(); // "ant move"
ant.breathe(); // "animal breathe"

디폴트 메소드로 선언한 것만으로 Ant에서의 미구현 에러는 사라집니다. 이어서 만약에 구현 클래스에 breathe()를 재정의하는 것이 필요하다면 재정의할 수 있습니다.

 

2. Animal을 구현한 클래스들에서 필요하다면 breathe()를 재정의합니다.

public class Ant implements Animal {
@Override
public void move() {
System.out.println("ant move");
}
@Override
public void breathe() {
System.out.println("ant breathe");
}
}
Animal ant = new Ant();
ant.move(); // "ant move"
ant.breathe(); // "animal breathe"

이렇게 Ant에서 재정의하면, 아직 Ant에는 필드가 없지만, 필요할 때 필드를 이용할 수 있습니다.

 

 

3. 시간이 지나고 Animal을 구현한 모든 클래스에서 breathe()를 구현했다면, Animal의 breathe()에서 default 키워드 및 구현체를 지우고, 일반 공개 인터페이스로 전환합니다.

public interface Animal { // no error, all implementations override all abstracts.
void move();
void breathe(); // default -> abstract
}
Animal ant = new Ant();
ant.move(); // "ant move"
ant.breathe(); // "ant breathe"

default를 지우고나면 추상메소드로 변경되었기 때문에, 이후에 추가될 Animal의 구현 클래스들은 반드시 breathe()를 구현해야 합니다.

 

 

위와 같은 방식으로 디폴트 메소드를 활용하면, 다소 번거롭더라도 구현 클래스에서 필드를 사용하지 못한다는 디폴트 메소드의 단점을 극복하며, 안전하게 하위호환성을 지키며 유연하게 설계를 확장할 수 있을 것입니다.


정적(static) 메소드

정적 메소드는 인스턴스에 종속되지 않고 타입에 종속되는 메소드입니다. 그래서 사실 진작에 인터페이스에 있었어도 확장에 문제를 일으키지 않기 때문에 개념적으로 전혀 무리가 없었습니다. 인터페이스에서는 Java 8부터 public으로 선언 및 구현이 가능합니다.

 

Java 9+에서의 변화

  1. 정적 메소드를 private으로도 선언이 가능합니다.
  2. 추가적으로 정적 메소드 및 디폴트 메소드의 구현의 편의를 위해 일반적인 private 메소드도 선언 및 구현이 가능해집니다.

 

정적 메소드의 활용 예시

인터페이스의 정적 메소드는 접근 제어자의 제한 외에 일반적인 정적 메소드와 활용상의 차이가 없습니다. 다음과 같이 클래스 참조로 호출할 수 있습니다.

public interface MyInterface {
static void staticMethod() {
System.out.println("staticMethod() of MyInterface");
}
}
MyInterface.staticMethod(); // "staticMethod() of MyInterface"

(참고) 권장되지는 않지만 클래스의 정적 메소드는 클래스 타입 변수로도 호출이 가능합니다. 그러나 인터페이스의 정적 메소드는 인터페이스의 타입 변수로는 호출이 불가능합니다.

더보기
public interface MyInterface {
static void staticMethod() {
System.out.println("staticMethod() of MyInterface");
}
}
public class MyInterfaceImpl implements MyInterface {}
public class MyClass {
public static void staticMethod() {
System.out.println("staticMethod() of MyClass");
}
}
MyInterface myInterface = new MyInterfaceImpl();
myInterface.staticMethod(); // error, 호출 불가
MyInterfaceImpl myInterfaceImpl = new MyInterfaceImpl();
myInterfaceImpl.staticMethod(); // error, 호출 불가
interface 타입 변수로 정적 메소드 호출시 발생하는 에러 메시지

 

MyClass myClass = new MyClass();
myClass.staticMethod(); // warning, "staticMethod() of MyClass"
클래스의 타입 변수로 정적 메소드 호출시 발생하는 경고 메시지