본문 바로가기
Programming/Languages (Java, etc)

[Java] Java 8 사용하기 : default method

by kghworks 2023. 9. 3.

목차

  • Interface를 업데이트하면 일어나는 일
  • Interface의 한계와 default method
  • default method 사용 예제
  • 심화 : resolution 규칙
  • 정리

 

 Java 8에 추가된 새로운 기능 default mehtod를 소개한다. Java 8부터 Interface에 static method, default method를 생성할 수 있게 되었고, 이미 public 하게 공개된 API Interface에 대해 더 편리하게 method를 추가할 수 있게 되었다. 

 라이브러리 개발자들은 public Interface API를 업데이트할 때 부담을 덜 수 있게 되었고, 라이브러리 사용자들은 Interface를 더 유연하고, 기능적으로 조합해서 구현할 수 있게 되었다.

 


Interface를 업데이트하면 일어나는 일

 

 라이브러리를 하나 만든다고 가정한다. 아니면 사내 공통 컴포넌트를 사용한다고 생각해도 좋다. 아래와 같이 도메인 class가 공통적으로 동작하는 method를 추상화해 놓은 Interface를 정의했다. 

 

/**
 * @Author : kgh
 * @Date : 2021-07-29
 * @Description : Domain class의 공통 기능을 interface로 뽑아냄
 * @Version : 1.0
 * */
public interface CommonActing {

    void whenCreated();

    void whenUpdt();

    void whenDel();

    void whenCopied();

    void whenMoved();
}

 

 이제 라이브러리를 배포했고, 다른 개발자들이 열심히 구현해서 사용하고있었다. 그리고 Interface를 업데이트할 일이 생겨, 메서드를 추가했다. 

 

/**
 * @Author : kgh
 * @Date : 2022-01-11
 * @Description : Domain class의 공통 기능을 interface로 뽑아냄
 * @Version : 1.1
 * */
public interface CommonActing {

    void whenCreated();

    ...
    
    // ver 1.1에 추가
    void whenRenamed();
}

 

 이제 CommanActing의 모든 구현체들에게서 compile errorr가 발생하기 시작한다. 

 

[Class Name] is not abstract and does not override abstract method whenRenamed() in CommonActing


Interface의 한계와 default method

 

 앞서 보았든 구현체들은 추가된 메서드를 구현하기 전까지 compile 할 수 없는 상황에 놓인다. Public API는 업데이트에 굉장히 조심스러우며, 특히 Interface는 목적 자체가 공통 기능의 추상화이지만 한번 공개된 API는 수정하기가 힘들어지게 된다.

 

Public API 업데이트시 고려해 볼 만한 compatibility (호환)는 binary, source, behavioral 세 가지 항목으로 나뉠 수 있다.

 

Compatibility Description interface에 method 추가하면
Binary 이미 컴파일된 코드의 실행 가능 여부 OK
Source 재 컴파일 가능 여부 No
compile error : 추가된 method 구현 필요
Behavioral
동일한 input에 대한 동일한 output 보장 여부 OK
추가된 method는 호출되지 않음

 

추가적으로 runitme 중에 구체 클래스를 호출하면 java.lang.AbastractMethodError 에러가 발생한다.

 

 public Interface에 대한 수정의 후폭풍은 이렇게도 크기 때문에, 라이브러리 배포자 혹은 사내 공통 컴포넌트 개발자라면 Interface를 공개하기 조차 망설여 질수도 있다. 

 

메서드가 추가된 Public Interface와 구현체의 compile error

 

 그럼에도 불구하고, native Java API들은 Java 8에서 기존 Interface에 성공적으로 method를 추가할 수 있었다. 성공적이라는 의미는 구체 클래스의 compile error를 유발하지 않았다는 것이다. java.util.Collection Interface의 stream(), java.util.List Interface의 sort()가 대표적이다. 이렇게 할 수 있었던 이유는 이 메서드들을 default method로 선언하였기 때문이다. 

 

 

deafult method

 Java 8에 추가된 새로운 method 선언 타입으로, Interface에 생성할 수 있다. default 키워드만 추가해서 생성하면 되고, 매력적인 건 기존의 구체 클래스들의 compile error를 발생시키지 않는다! 대표적인 Java API의 default method는 Colelction, List interface에 있다.

 

package java.util;

public interface Collection<E> extends Iterable<E> {
    ...
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false); // spliterator()는 Collection의 default method
    }
    ...
}

...

public interface List<E> extends Collection<E> {
    ...
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    ...
}

 

 또한 static method를 작성할 수 있게 되면서 Utilitly 목적의 class를 굳이 interface와 구체 클래스로 나눌 필요가 없어졌다. 

public interface CommonActingStatic {
    static void whenCreated() {
        // do something
    }

    static void whenUpdt() {
        // do something
    }
}

 

이제 Interface를 수정해도 구체 클래스에게 compile error를 유발하지 않으며, 필요에 따라 구체 클래스는 override 하여 사용할 수 있게 된다. 

 

/**
 * @Author : kgh
 * @Date : 2022-01-11
 * @Description : Domain class의 공통 기능을 interface로 뽑아냄
 * @Version : 1.1
 * */
public interface CommonActing {

    void whenCreated();

    ...
    
    // ver 1.1에 추가
    default void whenRenamed(){
       // do Something or override it
    };
}

 

abstract class와 Interface를 혼동하기도 하는데, abstract class는 class이다. 다중 상속이 불가능하다. 다음 사용 예제를 보면 이해할 수 있다. 

 

 


default method 사용 예제

 

 default method의 사용예제 첫번쨰는 기존 public API의 수정이다. 

 

default method를 추가한 v 1.1

 

인터페이스 구현만으로 기능 추가하기

 

다음으로는 class에 method를 추가할 때, body 구현, 재정의 없이 단순 구현 (implements) 만으로 하는 방식이다. 직접 보면 바로 이해가 간다. 

 

public interface Dancing{
    default void dance(){
        System.out.println("Dancing");
    }
}

public interface Singing{
    default void sing(){
        System.out.println("Singing");
    }
}

public interface Acting{
    default void act(){
        System.out.println("Acting");
    }
}

public class Idol implements Dancing, Singing, Acting{
}

public class Actor implements Acting{
}

...

Idol idol = new Idol();
idol.act(); // print "Acting"
idol.dance(); // print "Dancing"
idol.sing(); // print "Singing"

 

구현만으로 interface의 default method가 클래스에 추가될 수 있다. 다중 상속은 불가능한 Java는 abstract class나 일반 class를 이런 식으로 활용이 불가능하다. 그러나, 다중 구현은 가능하기 때문에 Interface를 구현하는 것만으로 interface의 default method를 추가할 수 있다. 

 


심화 : resolution 규칙

 

Interface를 구현하는 것만으로도 default method들을 상속받기 때문에, 몇가지 규칙 (rule)이 필요해졌다. 다음 코드를 보자. 

 

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}

public class C implements B, A {
    public static void main(String... args) {
        new C().hello(); // "Hello from B"
    }
}

 

이때 class C의 hello()를 호출하면 B의 default method를 상속받아 사용하게 된다. 이렇게 다중 구현을 통해 default method들이 겹칠 수 있기 때문에 확실한 기준이 정해져 있다. 다음과 같다.

 

규칙 1 : class, super class의 명시적인 method가 default method보다 우선순위가 높다
규칙 2 : Interface의 method signature가 동일한 method라면, 더 구체적인 method의 우선순위가 더 높다
규칙 3 :  규칙 2까지 같을 경우, 명시적으로 어떤 method를 사용할지 선택해야한다 (안 그러면 compile error 발생)

 

적용시켜 보자.

 

규칙 2 : Interface의 method signature가 동일한 method라면, 더 구체적인 method의 우선순위가 더 높다

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}

public class D implements A {
    // class `D`는 `A`를 구현하고, overriding 하지 않음
}

public class C extends D implements B, A {
    public static void main(String... args) {
        new C().hello(); // "Hello from B" (규칙 2)
    }
}

 C 가 구현, 상속한 것들 중에 B의 method가 가장 구체적이므로 B 의 hello()를 상속받았다.

 

규칙 3 :  규칙 2까지 같을 경우, 명시적으로 어떤 method를 사용할지 선택해야 한다 (안 그러면 compile error 발생)

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

public interface B {
    default void hello() {
        System.out.println("Hello from B");
    }
}

// comiple error : Error: class C inherits unrelated defaults for hello()from types B and A
public class C implements B, A {
}

 

규칙 2까지 모두 동등하므로 규칙 3을 적용해야 한다. 그렇지 않아서 compile error가 발생 중이다. 아래처럼 구현할 대상을 직접 명시해야 한다. 

 

public interface A {
    default void hello() {
        System.out.println("Hello from 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(); // "Hello from A" (규칙 3)
    }
}

정리

 

 Java 8에 추가된 default method를 살펴보았다. 라이브러리 개발자가 아니어도 Interface의 목적이 필요해서 선언하고 사용 중인 개발자라면, 매력적인 업그레이드라 볼 수 있다.

 

 그런데 사실 Effective Java에선 애초에 Public 접근 제어로 설정하는 거를 지양한다. 지양한다기보다는 가장 좁은 접근제어부터 설정하고 필요에 따라 한 단계씩 확장하기를 추천하고 있다. (private -> package-level -> public) ([참고] Effective Java item 15. 클래스와 멤버의 접근 권한을 최소화하라)

 

 public API는 말 그대로 개발자들이 맘대로 상속하고 구현해서 쓰라는 것인데, 문제는 그 이후에 API 업데이트가 필요해질 경우이다. (포스팅 서두에 봤던 것처럼) 따라서 default method를 적절히 사용할 수 있지만, 이미 동일한 signature를 사용 중이라면, 또다시 구체 클래스들에서 compile error를 맞이하게 된다.  따라서 final 키워드를 사용해서 애초에 상속을 금해버릴 수도 있다. 늘 그렇듯 정답 없이 적절하게 선택해서 사용하면 되겠다. 

 


참고

 

https://product.kyobobook.co.kr/detail/S000001033066

 

이펙티브 자바 | 조슈아 블로크 - 교보문고

이펙티브 자바 | 자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바

product.kyobobook.co.kr

https://product.kyobobook.co.kr/detail/S000001810171

 

모던 자바 인 액션 | 라울-게이브리얼 우르마 - 교보문고

모던 자바 인 액션 | 자바 1.0이 나온 이후 18년을 통틀어 가장 큰 변화가 자바 8 이후 이어지고 있다. 자바 8 이후 모던 자바를 이용하면 기존의 자바 코드 모두 그대로 쓸 수 있으며, 새로운 기능

product.kyobobook.co.kr

 

댓글