# 인터페이스의 변화

# 1. 인터페이스 기본 메소드와 스태틱 메소드

Java 8 부터는 인터페이스에도 메소드 선언뿐만 아니라 default methodstatic method를 통해 구현이 가능해졌다.

# 1-1. Default method (기본 메소드)

public interface Foo {
  void printName();
}
public class DefaultFoo implements Foo{
    @Override
    public void printName() {
        System.out.println("DefaultFoo");
    }
}

Foo 인터페이스를 구현하는 클래스가 많아지면, 새로운 기능의 메소드를 선언했이 추가됐을 때 구현체 모두 컴파일 에러가 발생.

이를 해결하기 위해, Default Method 등장!

public interface Foo {
    void printName();
		//기본 메소드(Default Method)
    default void printNameUpperCase(){
        System.out.println("FOO");
    }
}
public static void main(String[] args){
      Foo foo = new DefaultFoo();
      foo.printName();
      foo.printNameUpperCase();
}
  • 장점 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있음.
  • 주의할 점 기본 메소드는 구현체가 모르게 추가된 기능이기 때문에, 문제가 생길 수 있음.
public interface Foo {
    void printName();    
    default void printNameUpperCase() {
        System.out.println(getName().toUpperCase());
    }
    String getName(); // Name 값이 항상 있다고 보장할 수 없다.                        
    // name = null -> NullPointException
}
  • 컴파일 에러는 아니지만 구현체에 따라 런타임 에러 발생 가능
  • 반드시 문서화 (@ImplSpec 자바독 태그 사용)
/**
 * @ImplSpec
 * 이 구현체는 getName()으로 가져온 문자열을 대문자로 변환 후 출력한다.
 */
default void printNameUpperCase() {
    System.out.println(getName().toUpperCase());
}

이래도 문제가 발생하는 경우는 구현 클래스에서 Overriding 을 이용해 재정의 할 수 있다.

  • 주의할 점 Object가 제공하는 기능은 default 메소드로 제공할 수 없다.
    • equals, hashCode
    • 구현체가 재정의 해야한다!
    • 인터페이스에서 추상 메서드로 선언하는 것은 괜찮다.
      • 모두 공통으로 제공하기 때문에 추상 메서드로 분류하지 않는다.
  • 본인이 수정할 수 있는 인터페이스만 수정 가능하다.
  • 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다.

구현체가 상속받는 두 인터페이스 모두 동일한 기본 메소드가 있는 경우?

public interface Foo {
    default void getNameUpperCase() {
        ...
    }
}
public interface Bar extends Foo {
    void getNameUpperCase();  // 추상 메서드로 선언한 경우 구현 클래스에서 재정의 필수    
    // 추상 메서드로 선언 하지 않은 경우 상위 인터페이스 그대로 사용 가능
}
public interface Foo {
    default void getNameUpperCase() {
        System.out.println("Foo");    
    }
}
public interface Bar {
    default void getNameUpperCase() {
        System.out.println("Bar");    
    }
}
public class Test implements Foo, Bar {
    ...
      // @Override      
      // public void printNameUpperCase() {      
      //    ...      
      // }
}

Error: DefaultFoo inherits unrelated defaults for printNameUpperCase() from types Foo and Bar

  • Bar, Foo 중 무엇을 사용할지 모르기 때문에 컴파일 에러 발생
  • 충돌하는 default method 직접 override 해야한다.

# 1-2. Static method (정적 메소드)

해당 인터페이스를 구현한 모든 인스턴스나 해당 타입에 관련된 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 static method 제공 가능

public interface Foo {
    void printName();

    default void hello(){
        System.out.println("Foo");
    }
    static void helloAll(){
        System.out.println("인삿말");
    }
}
public class App {
    public static void main(String[] args) {
        Foo foo = new DefaultFoo();
        foo.hello();
        
        Foo.helloAll(); // static 메소드 사용
    }
}

# 2. 자바8 API의 기본 메소드와 스태틱 메소드

JAVA8 에서 추가된 기본 메소드로 인해 API 에 변화에 대해 알아보자

# 2-1. Iterable

공식 문서 (opens new window)

순회를 편하게 할 수 있다!

forEach()

public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("catsbi");
    names.add("hansol");
    names.add("toby");
    names.add("mijeong");

    names.forEach(System.out::println);
		
    // for (String name : names) {
    //    System.out.println(name);
    // }
}
  1. names.forEach(System.out::println)
    • forEach 는 내부 엘리먼트를 순회하며 각각의 요소들을 파라미터로 전달된 일급함수에 Functional Interface인 Consumer가 들어오고 처리.
    • 단순 출력만 하기 때문에 메소드 레퍼런스 기능을 이용해 간결하게 작성!
  2. for-of{...}
    • 기존에는 for-of 문을 사용해 출력이 가능하다고 함.
    • 하지만 조금이나마 더 간결한 forEach를 사용하는 것이 좋아 보임.

# 2-2. Collection

Iterable 을 상속받는 인터페이스다!

공식 문서 (opens new window)

spliterator

iterator 와 비슷한 개념이지만 Collection 을 분할한다는 점에서 차이가 있음.

public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("yj");
    names.add("youngjun");
    names.add("spring");
    names.add("java8");

    Spliterator<String> spliterator = names.spliterator();
    while(spliterator.tryAdvance(System.out::println));
}
  1. Spliterator spliterator = names.spliterator();
    • iterator와 비슷하지만 분할할 수 있는 기능을 가진 Iterator 를 만들어 반환
  2. spliterator.tryAdvance(System.out::println)
    • iterator의 hasNext()메소드와 유사
    • 다만 내부 메소드에 파라미터로 forEach()와 동일하게 Functional Interface인 Consumer가 들어오고 더 이상 들어올게 없을 경우 false를 반환
    • 이 예제에서는 들어온 값을 단순 출력하는 메소드 레퍼런스를 사용

여기까지는 iterator와 차이가 없다. trySplit을 사용해보자.

public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("yj");
    names.add("youngjun");
    names.add("spring");
    names.add("java8");

    Spliterator<String> spliterator = names.spliterator();
    Spliterator<String> trySplit = spliterator.trySplit();
    while(spliterator.tryAdvance(System.out::println));
    System.out.println("=========================");
    while(trySplit.tryAdvance(System.out::println));
}

/*
[실행 결과]
spring
java8
=========================
yj
youngjun
*/
  1. Spliterator<String> trySplit = spliterator.trySplit();

    • spliterator에서 trySplit()메소드를 호출하게되면 해당 spliterator에서 앞에서부터 절반의 요소를 꺼내 새로운 spliterator를 만들어 반환

    🔼 trySplit() 실행 결과

  2. while(spliterator.tryAdvance(System.out::println));

    • trySplit()을 통해 앞의 두 String(yj, youngjun)이 분할되어 빠져나갔기 때문에 뒤의 두 값만 출력
  3. while(trySplit.tryAdvance(System.out::println));

    • spliterator 에서 가져온 앞 두 값(yj, youngjun)을 출력

removeIf

Collection 요소를 순회하며 인자로 넘겨주는 함수를 Functional Interface인 Predicate에 넘겨줘서 true 이면 삭제

public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("yj");
    names.add("youngjun");
    names.add("spring");
    names.add("java8");

    names.removeIf(s -> s.startsWith("y"));
    names.forEach(System.out::println);
}
  1. names.removeIf(s -> s.startsWith("y"));
    • names를 순회하며 각 요소들 중 단어가 'y'로 시작하는 단어를 찾아 삭제

# 2-3. Comparator

공식문서 (opens new window)

정렬에 사용되는 인터페이스

reversed

정렬을 역순으로!

public static void main(String[] args) {
    List<String> names = new ArrayList<>();
    names.add("yj");
    names.add("youngjun");
    names.add("spring");
    names.add("java8");

    Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase;
    names.sort(compareToIgnoreCase.reversed());
    names.forEach(System.out::println);
}
  1. Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase
    • 메소드 레퍼런스를 메소드 체이닝 방식으로 사용할수는 없기에 분리하여 Comparator 타입의 Functional Interface인 compareToIgnoreCase 를 만들어준다.
  2. names.sort(compareToIgnoreCase.reversed());
    • 미리 선언해놓은 String 의 정렬 기준 메소드 레퍼런스에 reversed() 메소드를 호출해 역순으로 정렬

❓ 만약, 여기서 다음 정렬조건을 사용하여 정렬을 이어가고싶다면 thenComparing() 메소드를 이용하여 추가적인 정렬을 할 수 있다.


# Reference

Last Updated: 6/18/2023, 2:13:15 PM