본문으로 바로가기

1. 자바 8에서의 인터페이스의 변화

자바 8에서의 인터페이스의 변화로 큰 두 가지는 

 

인터페이스에 기본 메소드(Default Method) 스태틱 메소드(Static Method) 추가된 것입니다. 

 

이 변화로 인해 인터페이스 설계에 많은 변화가 생겼습니다.

 

자바 8이전에는 설계된 인터페이스를 구현 후 나중에 추가적으로 기능 추가에 대한 요구사항이 생기게 되면 

 

결국에 인터페이스에 새 기능(추상 메소드)를 추가하게 되는데, 

 

그 경우 모든 구현체에서 컴파일 에러가 발생하면서 새로운 추상 메소드를 따로 다 구현해야 되는 상황이 발생하게 됩니다.

 

그러나 자바 8에선 Default Method의 등장으로 새로 요구되는 기능들을 편하게 추가 할 수 있습니다.

2. 자바 8 인터페이스 예시

[인터페이스]

import java.util.Arrays;
import java.util.List;

public interface CanVendingMachine {

    //==자반기 음료수 리스트==//
    List list = Arrays.asList("코카콜라", "펩시콜라", "스프라이트");

    //==현금결제 추상 메소드==//
    void moneyPayment(String money);

    //==음료선택 추상 메소드==//
    void drinkSelect(String can);

    //==카드결제 기본 메소드==//
    default void CreditCardPayment(String card) {
        if(card.equals("삼성카드")) {
            System.out.println("결제가 가능합니다.");
        } else {
            System.out.println("결제가 불가능한 카드입니다.");
        }
    }

    //==음료 리스트 출력 스태틱 메소드==//
    static void DrinkLookup() {
        System.out.println("음료의 종류는 다음과 같습니다.");
        list.forEach(System.out::println);
    }
}

 

[구현체]

public class MachineImpl implements CanVendingMachine {

    @Override
    public void moneyPayment(String money) {
        System.out.println("잔액: " + money);
    }

    @Override
    public void drinkSelect(String can) {
        System.out.println(can + " 선택");
    }
}

3. 기본 메소드 (Default Method)

  • 인터페이스에 추상 메소드 선언이 아니라 구현체를 제공하는 방법입니다.
  • 인터페이스의 구현체 작성 시 재정의(오버라이드) 할 필요 없이 공통 기능을 편하게 개발이 가능합니다.
  • 인터페이스의 구현체에서 재정의 할 수도 있습니다. (위에 예시에서 기본 메소드인 CreditCardPayment를 재정의 하였습니다.) 
  • 단, 구현체가 모르게 구현이 된 기능으로 리스크도 존재합니다. (매우 강력하지만 주의해서 사용해야 할 기능)

 

[기본 메소드 오버라이드]

public class MachineImpl implements CanVendingMachine {

    // ( ... ) 생략

    // 기본 메소드는 구현체에서 오버라이드하여 재정의 할 수 있습니다.
    @Override
    public void CreditCardPayment(String card) {

        if(card.equals("삼성카드") || card.equals("국민카드")) {
            System.out.println("결제가 가능합니다.");
        } else {
            System.out.println("결제가 불가능한 카드입니다.");
        }
    }
}

 

[리스크]

런타임 에러 발생 가능성

-> 구현체에서 기본 메소드로 잘 못된 매개변수로 호출할 경우 예기치 못한 런타임 에러가 발생할 수 있습니다.

public class MachineImpl implements CanVendingMachine {

    // ( ... ) 생략

    // 기본 메소드는 구현체에서 오버라이드하여 재정의 할 수 있습니다.
    @Override
    public void CreditCardPayment(String card) {

        if(card.equals("삼성카드") || card.equals("국민카드")) {
            System.out.println("결제가 가능합니다.");
        } else {
            System.out.println("결제가 불가능한 카드입니다.");
        }
    }

    public static void main(String[] args) {
        MachineImpl machine = new MachineImpl();
        machine.CreditCardPayment(null); // equals 실행 시 NullPointerException 발생
    }
}

 

[조치 방법]

@implSpec 자바독 태그 사용하여 꼭 문서화를 해 줍시다. 그리고 재정의를 이용해서 해결 할 수 있습니다.

public interface CanVendingMachine {

    // ( . . . ) 생략

    /**
     * @implSpec
     * 이 구현체는 카드 결제 가능 유무 체크 기능입니다.
     * 주의: Null data로 호출 하면 안됩니다.
     */
    //==카드결제 기본 메소드==//
    default void CreditCardPayment(String card) {
        if(card.equals("삼성카드")) {
            System.out.println("결제가 가능합니다.");
        } else {
            System.out.println("결제가 불가능한 카드입니다.");
        }
    }
}
public class MachineImpl implements CanVendingMachine {

    // ( ... ) 생략

    // 기본 메소드는 구현체에서 오버라이드하여 재정의 할 수 있습니다.
    @Override
    public void CreditCardPayment(String card) {

        try {
            if(card.equals("삼성카드") || card.equals("국민카드")) {
                System.out.println("결제가 가능합니다.");
            } else {
                System.out.println("결제가 불가능한 카드입니다.");
            }
        } catch (NullPointerException e) {
            System.out.println("결제가 불가능한 카드입니다.");
        }
    }

    public static void main(String[] args) {
        MachineImpl machine = new MachineImpl();
        machine.CreditCardPayment(null); // equals 실행 시 NullPointerException 발생
    }
}

4. 스태틱 메소드 (Static Method)

  • 기본적인 메서드는 인스턴스가 사용할 수 있는 것이고 해당 인터페이스를 구현한 모든 인스턴스, 해당 타입에 관련되어있는 유틸리티나 헬퍼 메서드를 제공하고 싶은 경우에는 static 메서드를 제공할 수 있습니다.
  • 당연한 이야기이지만 Static Method는 구현체에서 재정의 할 수 없습니다.
  • 헬퍼 클래스
    • 개발자의 편의를 위한 클래스로 어떤 기능을 간편하게 수행하기 위해 사용합니다.
  • 유틸리티 클래스
    • 인스턴스 메서드와 인스턴스 변수를 제공하지 않고, 정적 메서드와 변수만을 제공하는 클래스로 편의를 위해 사용합니다.

5. 인터페이스의 변화로 인한 API의 변화

  • 인터페이스에 기본 메소드의 등장으로 기존 API에 여러 기능들이 추가되었습니다.
  • Collection 같은 경우에는 자바 8의 강력한 기능인 stream()과 parallelStream()이 추가되었습니다.
public interface Collection<E> extends Iterable<E> {

    // ( . . . ) 생략

    /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a sequential {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a sequential {@code Stream} over the elements in this collection
     * @since 1.8
     */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    /**
     * Returns a possibly parallel {@code Stream} with this collection as its
     * source.  It is allowable for this method to return a sequential stream.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a parallel {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a possibly parallel {@code Stream} over the elements in this
     * collection
     * @since 1.8
     */
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }    
}