Java 8 인터페이스 변경 사항에는 인터페이스에 정적 메서드와 기본 메서드가 포함됩니다. Java 8 이전에는 인터페이스에서 메서드 선언만 가능했습니다. 그러나 Java 8부터는 인터페이스에 기본 메서드와 정적 메서드를 추가할 수 있습니다.
Java 8 인터페이스
인터페이스를 설계하는 것은 항상 어려운 작업이었습니다. 인터페이스에 추가적인 메서드를 추가하려면 모든 구현 클래스를 변경해야 했습니다. 인터페이스가 오래되면 구현하는 클래스의 수가 많아져 인터페이스를 확장하는 것이 불가능할 수도 있습니다. 이것이 애플리케이션을 설계할 때 대부분의 프레임워크가 기본 구현 클래스를 제공하고, 우리의 애플리케이션에 적용 가능한 메서드를 재정의하기 위해 이를 확장하는 이유입니다. 이제 기본 인터페이스 메서드와 정적 인터페이스 메서드, 그리고 Java 8 인터페이스 변경 사항의 도입 이유를 살펴보겠습니다.
Java 인터페이스 기본 메서드
자바 인터페이스에서 기본 메서드를 생성하려면 메서드 시그니처와 함께 “default” 키워드를 사용해야 합니다. 예를 들어,
package com.journaldev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
log(String str)은 Interface1
에서 기본 메서드입니다. 이제 클래스가 Interface1을 구현할 때 인터페이스의 기본 메서드에 대한 구현은 필수가 아닙니다. 이 기능은 인터페이스에 추가 메서드를 확장하는 데 도움이 됩니다. 기본 구현만 제공하면 됩니다. 다음과 같은 메서드를 가진 다른 인터페이스가 있다고 가정해 봅시다.
package com.journaldev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
우리는 Java가 여러 클래스를 확장하는 것을 허용하지 않는다는 것을 알고 있습니다. 이로 인해 “다이아몬드 문제”가 발생하며 컴파일러는 어떤 수퍼 클래스 메서드를 사용해야 할지 결정할 수 없습니다. 기본 메서드로 인해 인터페이스에서도 다이아몬드 문제가 발생할 것입니다. 왜냐하면 클래스가 Interface1과 Interface2를 모두 구현하고 공통 기본 메서드를 구현하지 않는 경우 컴파일러는 어떤 것을 선택해야 할지 결정할 수 없습니다. 여러 인터페이스를 확장하는 것은 Java의 핵심 Java 클래스 및 대부분의 엔터프라이즈 응용 프로그램 및 프레임워크에서 중요한 부분입니다. 따라서 인터페이스에서 이러한 문제가 발생하지 않도록 하기 위해 인터페이스의 공통 기본 메서드에 대한 구현을 필수로 제공해야 합니다. 따라서 클래스가 위의 두 인터페이스를 모두 구현하는 경우 log()
메서드의 구현을 제공해야 합니다. 그렇지 않으면 컴파일러가 컴파일 시간 오류를 발생시킵니다. Interface1
과 Interface2
를 모두 구현하는 간단한 클래스는 다음과 같습니다.
package com.journaldev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
자바 인터페이스 기본 메서드에 대한 중요한 포인트:
- 자바 인터페이스의 기본 메소드는 구현 클래스를 깨뜨릴 염려 없이 인터페이스를 확장하는 데 도움이 됩니다.
- 자바 인터페이스의 기본 메소드는 인터페이스와 추상 클래스 간의 차이를 줄였습니다.
- 자바 8의 인터페이스 기본 메소드는 유틸리티 클래스를 피하는 데 도움이 됩니다. 예를 들어, 모든 Collections 클래스 메소드는 인터페이스 자체에서 제공될 수 있습니다.
- 자바 인터페이스의 기본 메소드는 기본 구현 클래스를 제거하는 데 도움이 됩니다. 기본 구현을 제공하고 구현 클래스는 어떤 것을 재정의할지 선택할 수 있습니다.
- 인터페이스에서 기본 메소드를 도입한 주요한 이유 중 하나는 자바 8에서 람다 표현식을 지원하기 위해 컬렉션 API를 향상시키는 것입니다.
- 계층 구조의 클래스 중에서 동일한 시그니처를 가진 메소드가 있는 경우 기본 메소드는 관련이 없어집니다. 기본 메소드는 java.lang.Object에서 메소드를 재정의할 수 없습니다. 이유는 매우 간단합니다. Object는 모든 자바 클래스의 기본 클래스이기 때문입니다. 따라서 인터페이스에서 Object 클래스 메소드를 기본 메소드로 정의해도 무의미하게 됩니다. 항상 Object 클래스 메소드가 사용되기 때문에 혼란을 피하기 위해 Object 클래스 메소드를 재정의할 수 없습니다.
- 자바 인터페이스의 기본 메소드는 Defender 메소드 또는 가상 확장 메소드로도 알려져 있습니다.
자바 인터페이스 정적 메소드
Java 인터페이스 정적 메소드는 기본 메소드와 유사하지만 구현 클래스에서 재정의할 수 없습니다. 이 기능은 구현 클래스에서 잘못된 구현으로 인한 원하지 않는 결과를 피하는 데 도움이 됩니다. 간단한 예제로 살펴보겠습니다.
package com.journaldev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
이제 구현 클래스를 살펴보겠습니다. isNull() 메소드가 잘못된 구현을 가지고 있는 클래스입니다.
package com.journaldev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
isNull(String str)
는 간단한 클래스 메소드로, 인터페이스 메소드를 재정의하는 것이 아닙니다. 예를 들어, isNull() 메소드에 @Override 어노테이션을 추가하면 컴파일 오류가 발생합니다. 이제 애플리케이션을 실행하면 다음과 같은 결과가 나옵니다.
Interface Null Check
Impl Null Check
인터페이스 메소드를 정적에서 기본으로 변경하면 다음과 같은 결과가 나옵니다.
Impl Null Check
MyData Print::
Impl Null Check
Java 인터페이스 정적 메소드는 인터페이스 메소드에서만 사용할 수 있습니다. MyDataImpl
클래스에서 isNull() 메소드를 제거하면 MyDataImpl
객체에 사용할 수 없습니다. 그러나 다른 정적 메소드와 마찬가지로 클래스 이름을 사용하여 인터페이스 정적 메소드를 사용할 수 있습니다. 예를 들어, 유효한 문장은 다음과 같습니다:
boolean result = MyData.isNull("abc");
Java 인터페이스 정적 메소드에 대한 중요한 점:
- Java 인터페이스 정적 메소드는 인터페이스의 일부이므로 구현 클래스 객체에 사용할 수 없습니다.
- Java 인터페이스 정적 메소드는 유틸리티 메소드(예: null 체크, 컬렉션 정렬 등)를 제공하는 데 좋습니다.
- Java 인터페이스의 정적 메소드는 구현 클래스가 이를 오버라이드하지 못하도록하여 보안을 제공하는 데 도움이 됩니다.
- Object 클래스의 메소드에 대해서는 인터페이스 정적 메소드를 정의할 수 없으며, “This static method cannot hide the instance method from Object”라는 컴파일러 오류가 발생합니다. 이는 java에서 허용되지 않기 때문에 모든 클래스의 기본 클래스인 Object에 대해 클래스 수준의 정적 메소드와 동일한 시그니처를 가진 다른 인스턴스 메소드를 가질 수 없기 때문입니다.
- 우리는 java 인터페이스의 정적 메소드를 사용하여 유틸리티 클래스인 Collections와 같은 것을 제거하고 해당 인터페이스로 모든 정적 메소드를 이동할 수 있습니다. 이렇게하면 찾고 사용하기가 쉬워집니다.
Java 기능적 인터페이스
포스트를 마무리하기 전에 함수형 인터페이스에 대해 간단히 소개하고자 합니다. 정확히 하나의 추상 메서드를 가지는 인터페이스를 함수형 인터페이스라고 합니다. 새로운 어노테이션 @FunctionalInterface가 함수형 인터페이스를 표시하기 위해 도입되었습니다. @FunctionalInterface 어노테이션은 함수형 인터페이스에 추상 메서드가 실수로 추가되는 것을 방지하기 위한 기능입니다. 이는 선택 사항이지만 사용하는 것이 좋은 실천법입니다. 함수형 인터페이스는 Java 8의 오랜 기다림과 많은 요구에 따라 도입되었으며, 이를 통해 람다 표현식을 사용하여 인스턴스화할 수 있습니다. 새로운 패키지 java.util.function
에는 람다 표현식과 메서드 참조의 대상 타입을 제공하기 위해 다양한 함수형 인터페이스가 추가되었습니다. 우리는 나중에 함수형 인터페이스와 람다 표현식에 대해 자세히 살펴볼 것입니다.