interface의 default method와 static method

Posted by yunki kim on November 20, 2021

  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를 이용이 이 문제를 해결할 수 있다.