Java8부터 interface에 default method와 static method를 사용할 수 있게 되었다. Default method와 static method는 해당 interface를 implements하는 모든 인스턴스가 같은 기능이 있었으면 좋겠다는 이유로 추가되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
public interface Foo {
/**
* 이름을 출력하는 추상 메서드
*/
void printName();
/**
* 이름을 대문자로 출력하는 메서드
*/
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
/**
* 이름을 가져오는 메서드
* @return String 이름을 반환
*/
String getName();
}
public class DefaultFoo implements Foo{
/**이름을 저장*/
String name;
/**
* DefaultFoo 생성자
* @param name 이름을 받는 파라미터
*/
public DefaultFoo(String name) {
this.name = name;
}
/**
* implements한 Foo 인터페이스의 getName 추상 메서드 구현체
* @return String 멤버 변수 name을 반환
*/
@Override
public String getName() {
return this.name;
}
/**
* implements한 Foo 인터페이스의 printName 추상 메서드 구현체
*/
@Override
public void printName() {
System.out.println(this.name);
}
}
public class App {
/**
* 실습에 필요한 DefaultFoo의 인스턴스를 만들고 결과를 출력한다
* @param args Unused
*/
public static void main(String[] args) {
Foo foo = new DefaultFoo("yunki");
foo.printName();
foo.printNameUpperCase();
}
}
|
cs |
Default method를 사용하게 되면 해당 인터페이스를 implements한 모든 객체에서 사용 가능하지만 이 기능을 제대로 동작할 것이라는 보장이 없다. 왜냐면 getName()의 구현체에서 어떤 값이 실제로 반환될지 모르기 때문이다. 따라서 이를 방지하기 위해 문서화를 철저히 해야한다. 이때 java8에서 추가된 @impleSpec을 사용한다.
1
2
3
4
5
6
7
|
/**
* @implSpec
* 이름을 대문자로 출력하는 메서드
*/
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
|
cs |
만약 이런 식으로 문서화를 했음에도 문제가 된다면 override를 하면 된다.
Object에서 제공하는 메서드들은 default method로 재정의할 수 없다(추상 메서드는 가능).
1
2
|
//Default method 'toString' overrides a member of 'java.lang.Object'
default String toString() {}
|
cs |
만약 인터페이스가 제공하는 default method를 사용하고 싶지 않다면 다른 인터페이스를 만들고 해당 인터페이스를 extends 한뒤 defualt method를 추상 메서드로 만들면 된다.
1
2
3
|
public interface Bar extends Foo{
void printNameUpperCase();
}
|
cs |
만약 Foo interface, Bar interface가 둘 다 같은 default method를 가지고 있다면 두가지를 동시에 implements할 수 없다. 해야 한다면 implements를 한 클래스에서 override를 해야 한다.
static method
해당 인터페이스를 implements하는 객체가 helper mthod(메서드의 작업을 도와주는, 반복적으로 사용되는 짧은 메서드)나 유틸리티가 필요할 경우 사용된다.
1
2
3
|
static void printAnyThing() {
System.out.println("Foo");
}
|
cs |
1
2
3
4
5
6
|
public static void main(String[] args) {
Foo foo = new DefaultFoo("yunki");
foo.printName();
foo.printNameUpperCase();
Foo.printAnyThing();
}
|
cs |
java 8에서 추가된 default method
Iterable의 default method
forEach(): js의 forEach처럼 리스트를 순회한다. forEach는 Consumer를 인자로 받는다.
1
2
3
4
5
6
7
8
9
|
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("yunki");
names.add("skull");
names.add("keesun");
names.add("toby");
names.forEach(System.out::println);
}
|
cs |
spliterator(): forEach같이 순회를 할 수도 있고 리스트를 여러개로 쪼갤 수 도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("yunki");
names.add("skull");
names.add("keesun");
names.add("toby");
Spliterator<String> spliterator = names.spliterator();
Spliterator<String> spliterator1 = spliterator.trySplit();
//tryAdvance역시 Consumer를 인자로 받는다.
while(spliterator.tryAdvance(System.out::println)); //keesun, toby
System.out.println("=======");
while(spliterator1.tryAdvance(System.out::println)); //yunki, skull
}
|
cs |
Collection의 default method
stream() / parallelStream() : Collection의 모든 하위 인터페이스들은 stream을 가지고 있다.
stream은 내부적으로 spliterator를 사용한다. stream은 리스트 내부의 엘리먼트들을 스트림으로 만들어서 functional하게 처리를 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("yunki");
names.add("skull");
names.add("keesun");
names.add("toby");
Set<String> streamResult = names.stream()
.map(String::toUpperCase)
.filter(s -> s.startsWith("K"))
.collect(Collectors.toSet());
streamResult.forEach(System.out::println);
}
|
cs |
removeIf(Predicate): 조건에 만족하는 엘리먼트를 제거한다. 인가로 Predicate를 받는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class App {
/**
* java8 공식 API에서 추가된 default method 실습
* @param args Unused
*/
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("yunki");
names.add("skull");
names.add("keesun");
names.add("toby");
names.removeIf(name -> name.startsWith("k"));
names.forEach(System.out::println);
}
}
|
cs |
Comparator의 default method, static method
reversed(): 내림차순으로 엘리먼트를 비교한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class App {
/**
* java8 공식 API에서 추가된 default method 실습
* @param args Unused
*/
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("yunki");
names.add("skull");
names.add("keesun");
names.add("toby");
//오름차순 정렬
names.sort(String::compareToIgnoreCase);
names.forEach(System.out::println);
System.out.println("==========");
//내림차순 정렬
Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase;
names.sort(compareToIgnoreCase.reversed());
names.forEach(System.out::println);
}
}
|
cs |
thenComparing(): 기본적인 비교를 한 뒤 또 다른 조건으로 비교를 하고 싶을때 사용한다
static reverseOrder() / naturalOrder()
static nullsFirst() / nullLast(): null이 우선순위를 가진다 / null의 우선순위가 가장 뒤이다.
static comparing()
Default method가 java api에 가져온 변화
하나의 인터페이스가 있다고 해보자. java8이전에는 서로 다른 객체가 이 인터페이스를 조금 더 편리하게 상요하기 위해 아래 그림과 같이 인터페이스를 implements하는 추상 클래스를 만들고 이 추상클래스를 상속받아 사용했다.
이런방식을 사용하면 굳이 구현이 필요없는 추상 메서드의 구현체를 구현할 필요가 없어진다. 하지만 자바에서는 오직 하나의 객체만을 상속받을 수 있기 때문에 상속을 더이상 받을 수 없는 문제가 발생하게 된다.
java 8이상을 사용하게 되면 default method를 이용이 이 문제를 해결할 수 있다.