티스토리 뷰
07 : 디폴트 메서드
1 2 | for(int i = 0 ; i < list.size(); i++) System.out.println(list.get(i)); |
이를 함수형을 이용하여 바꾸면
1 | list.forEach(System.out::println); |
메서드 레퍼런스로 전달 했음을 잊지 말자 !
자바 Collection 인터페이스에 forEach란 새로운 메서드를 얻게 되면, Collection을 구현하는 자체 클래스를 정의 하고 있는 모든 프로그램은 이 메서드를 구현 해야만 한다. 얼마나 비 효율 적이고 짜증나는 일인가 ... 상상만 해도 귀찮은 일이다.
그래서 자바 설계자들은 구체적인 구현을 담은 인터페이스 메서드(Default Method)를 허용해 이를 해결 하였다.
디폴트 메서드는 기존 인터페이스에 안전하게 새로운 메서드를 추가 할 수 있다.
Note : 자바 8에서는 이 절에서 설명하는 매커니즘을 통해 Collection의 슈퍼 인터페이스인 Iterable 인터페이스에 forEach 메서드를 추가했다.
다음은 예제 이다.
1 2 3 4 | interface Person{ long getId(); default String getName() { return "John Q. Public";} } |
이 인터페이스는 추상 메서드 getId와 디폴트 메서드 getName을 포함하고 있다.
당연히 Person 인터페이스의 구현하는 구체 클래스(Concrete class)는 getId의 구현을 제공해야 하지만, getName의 구현은 그대로 두거나 오버라이드하면된다.
디폴트 메서드는 인터페이스와 해당 인터페이스의 대부분 혹은 모든 메서드를 구현한 추상 클래스를 제공하는 고전적인 패턴의 종말을 선고 했단다.
만약 동일한 메서드가 한 인터페이스의 디폴트 메서드로 정의되어 있고, 슈퍼 클래스나 다른 인터페이스의 메서드도 정의되면 무슨일이 일어 날까? 스칼라나 C++ 같은 언어는 이와 같은 모호함을 해결하는 복잡한 규칙을 갖추고 있지만 다행히 자바에서는 간단한 규칙을 가지고 있다.
1. 슈퍼클래스가 우선한다. 슈퍼클래스에서 구체적인 메서드를 제공하는 경우, 이와 이름 및 파라미터 타입이 같은 디폴트 메서드는 단순히 무시된다.
2. 인터페이스들이 충돌한다. 어떤 슈퍼인터페이스에서 디폴트 메서드를 제공하고, 또 다른 인터페이스에서 이름 및 파라미터 타입이 같은 메서드를 제공하는 경우에는 해당 메서드를 오버라이드 해서 충돌을 해결해야 한다.
그럼 이제 두번째 규칙을 살펴 보자. getName 메서드를 포함하는 또 다른 인터페이스를 정의 해 보자.
1 2 3 4 5 | interface Named{ default String getName(){ return getClass().getName() + "_" + hashCode(); } } |
이제 다음과 같이 두 인터페이스를 모두 구현하는 클래스를 정의 해 보면 무슨일이 일어 날까?
1 2 3 | class Student implements Person, Named{ ... } |
Student 클래스는 Person과 Named 인터페이스가 제공하는 두 가지 모순되는 getName 메서드를 상속한다. 자바는 이 중 하나를 우선해서 선태갛기보다는 오류를 보고하고 프로그래머에게 모호함을 해결하도록 맡긴다.
단순히 Student 클래스에 getName 메서드를 제공하면 이 메서드 안에서 다음처럼 두 충돌중 하나의 메서드를 선택 할 수 있다.
1 2 3 4 | class Student implements Person, Named { public String getName() {return Person.super.getName();} ... } |
1 2 3 | interface Named{ String getName(); } |
Student 클래스가 Person 인터페이스로부터 디폴트 메서드를 상속은 자바 설계자들의 일관성을 위해 지원하지 않는다고 한다.
두 인터페이스가 어떻게 충돌하는지는 문제가 되지 않는다. 적어도 한 인터페이스에서 구현을 제공하면 컴파일러는 오류를 보고하며, 프로그래머는 모호함을 해결해야한다.
이제 슈퍼 클래스를 확장 하면서 인터페이스 하나를 구현하여, 이 둘에서 같은 메서드를 상속하는 클래스를 고려해보자.
1 | class Student extends Person implements Named { ... } |
이 경우엔 오직 슈퍼클래스의 메서드만 중요하고, 인터페이스의 모든 디폴트 메서드는 단순히 무시 된다. 이 예제에서 Student는 Person에서 getName 메서드를 상속하며 Named 인터페이스에서 getName의 디폴트 구현 제공 여부는 아무 상관이 없다. 이것이 바로 클래스 우선 규칙이다.
Caution : Object 클래스에 있는 메서드 중 하나를 재정의하는 디폴트 메서드는 만들수 없다. 예를들어, List 같은 인터페이스에서 toString이나 equals에 해당하는 디폴트 메서드가 매력적인 방법일 수 있지만 정의 불가능 하다. 이는 '클래스 우선' 규칙의 결과로, 이러한 메서드는 결코 Object.toString 이나 Object.equals보다 우선 할수 없다.
<소스코드>
https://github.com/JayStevency/Java8/tree/master/src/com/java8/defaultmethod
'Language > Java' 카테고리의 다른 글
[JAVA8] 스트림 API (0) | 2017.01.31 |
---|---|
[JAVA8] 람다 표현식 (0) | 2017.01.29 |
[JAVA8] 람다 표현식 (0) | 2017.01.24 |
[JAVA8] 람다 표현식 (0) | 2017.01.24 |
[JAVA8] 람다 표현식 (0) | 2017.01.18 |