✍️인터페이스 default 메서드
default 메서드 예제
자바 8부터 인터페이스에 default 메서드와 static 메서드가 추가됐다.
public interface Foo {
void printName();
}
public class DefaultFoo implements Foo {
String name;
@Override
public void printName() {
System.out.println(name);
}
}
여러 클래스에서 Foo를 구현했다고 가정하고,
어느 날 Foo를 구현한 클래스에 공통적으로 제공할 기능이 추가됐으면 좋겠다 생각이 들어 Foo에 추상 메서드를 추가했다.
public interface Foo {
void printName();
void printNameUpperCase();
}
그 순간 Foo를 구현한 모든 구현체에서 에러 메시지가 출력되기 시작한다...
에러가 발생하지 않고 공통 기능을 추가할 수 있는 방법이 default 메서드를 활용하는 방법이다.
public interface Foo {
void printName();
String getName();
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
}
public class DefaultFoo implements Foo {
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(name);
}
@Override
public String getName() {
return this.name;
}
}
public static void main(String[] args) {
Foo foo = new DefaultFoo("kang");
foo.printName();
foo.printNameUpperCase();
}
// kang
// KANG
주의점
주의점은 default로 제공되는 기능이 모든 구현체의 인스턴스에게 항상 제대로 동작한다는 보장이 없다.
예제만 봐도 getName에서 무슨 값이 반환될지 모른다. 실제로 getName이 null을 반환한다면 RuntimeException이 발생한다.
그렇다면 default 메서드는 사용하면 안 되는 걸까? 그렇지 않다. default 메서드를 방어적으로 사용하도록 하자.
첫 번째는 @implSpec 어노테이션을 사용해 문서화를 잘 하는 방법이다.
public interface Foo {
void printName();
String getName();
/**
* @implSpec
* getName()으로 가져온 문자열을 대문자로 바꿔 출력한다.
*/
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
}
두 번째는 default 메서드가 문제가 된다면 구현체에서 재정의 할 수 있다.
public class DefaultFoo implements Foo {
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(name);
}
@Override
public String getName() {
return this.name;
}
@Override
public void printNameUpperCase() {
System.out.println(this.name.toUpperCase());
}
}
제약사항
default 메서드의 제약사항으로는 Object가 제공하는 메서드는 default 메서드로 제공할 수 없다.
public interface Foo {
default String toString() {
}
}
Error : A default method cannot override a method from java.lang.Object
default 메서드의 상속
default 메서드를 가진 인터페이스를 상속받는 인터페이스에서 default 메서드를 다시 추상 메서드로 변경할 수 있다.
public class DefaultFoo implements Foo {
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(name);
}
@Override
public String getName() {
return this.name;
}
@Override
public void printNameUpperCase() {
System.out.println(this.name.toUpperCase());
}
}
public interface Bar extends Foo {
void printNameUpperCase();
}
단, Bar의 구현체들은 반드시 추상 메서드인 printNameUpperCase를 재정의해야 한다.
다이아몬드 문제
Foo와 Bar 인터페이스에 동일한 이름의 default 메서드가 있고 구현체가 동시에 두 인터페이스를 구현하려 한다면 다이아몬드 문제에 빠져 컴파일 에러가 발생한다.
public interface Foo {
default void printNameUpperCase() {
System.out.println("Foo");
}
}
public interface Bar {
default void printNameUpperCase() {
System.out.println("Bar");
}
}
public class DefaultFoo implements Foo, Bar {
}
Error : Duplicate default methods named printNameUpperCase with the parameters () and () are inherited from the types Bar and Foo
구현체에서 중복된 default 메서드를 override함으로서 다이아몬드 문제를 해결할 수 있다.
public class DefaultFoo implements Foo, Bar {
@Override
public void printNameUpperCase() {
}
}
🍊인터페이스 static 메서드
default 메서드는 구현체의 인스턴스가 사용할 메서드라면, static 메서드는 인터페이스 자체에서 제공할 헬퍼 또는 유틸리티 메서드를 구현할 때 사용한다.
public static void main(String[] args) {
Foo.printAnything();
}
// anything
'Java > Java 8' 카테고리의 다른 글
[Java8] Chapter 3-2. Stream API 실습 (2) (0) | 2022.02.20 |
---|---|
[Java8] Chapter 3-1. Stream API (1) (0) | 2022.02.18 |
[Java8] Chapter 1-4. 메서드 레퍼런스 (0) | 2022.02.16 |
[Java8] Chapter 1-3. 람다 표현식과 변수 캡쳐 (0) | 2022.02.08 |
[Java8] Chapter 1-2. Java에서 제공하는 함수형 인터페이스 (0) | 2022.02.07 |