디폴트 메서드
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"가 출력된다. (이를 '다이아몬드 문제'라고 부른다)
'Java' 카테고리의 다른 글
정적팩토리 메서드 명명 규칙 (0) | 2020.05.14 |
---|---|
Optional 정리 (0) | 2020.04.18 |
람다 표현식을 활용한 디자인패턴 - 팩토리 패턴 (0) | 2020.04.14 |
람다 표현식을 활용한 디자인패턴 - 의무 체인 패턴 (0) | 2020.04.11 |
람다 표현식을 활용한 디자인패턴 - 옵저버 패턴 (0) | 2020.04.08 |