ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] 인터페이스 default method (java 8)
    프로그래밍 언어/JAVA 2022. 9. 4. 21:52

    오늘은 java 8에서 추가된 기능 중 하나인 인터페이스에서의 default method에 대해 정리해보려고 한다.

     

    현재 java의 최신 LTS버전은 17이지만 8버전 때의 업데이트에서는 다양한 기능들이 추가되었었고 java의 기본적인 공부를 할 때에 8버전 때에 추가된 기능들을 하나하나 알아보는 것도 중요하다고 생각한다.

     

     

    왜 default method가 추가되었을까?

    오라클에서 제공하는 java 공식문서에서는 아래와 같은 예시를 들며 default method의 필요성을 설명했다.

    computer-controlled car 제조업체들이 자동차에 비행과 같은 새로운 기능을 추가한다면 어떠할까?
    computer-controlled car 제조업체들은 다른 회사가 자사의 소프트웨어를 하늘을 나는 자동차에 적용할 수 있도록 새로운 메서드를 명시해야 한다.
    computer-controlled car 제조업체들은 새로운 비행 관련 메서드들을 어디에 선언할 것인가?
    만약 해당 메서드들을 원래 인터페이스에 추가한다면, 그 인터페이스들을 구현한 프로그래머들은 구현한 부분을 다시 써야 할 것이다.
    만약 해당 메서드들을 static method로 추가한다면, 프로그래머들은 해당 메서드들을 필수적이고 core한 메서드가 아닌 유틸리티한 메서드로 간주할 것이다.

    default method를 사용하면 라이브러리의 인터페이스에 새 기능을 추가하고 해당 인터페이스의 이전 버전을 구현한 코드와의 호환성을 보장할 수 있다.

    즉, 추상메서드로 선언하기에는 이미 해당 인터페이스를 구현한 모든 클래스에서의 수정이 필요하다는 단점이 생기고 static method로 선언하기에는 해당 메서드의 성격이 전달되지 못한다는 단점이 존재하기에 새로운 default method를 도입했다는 것이다.

     

     

    default method 사용 예시 

    public interface ExampleInterface {
    
        void printName();
    
        String getName();
        
        default void printNameUpperCase() {
        	System.out.println(getName().toUpperCase());
        };
    
    }

    위 코드와 같이 default 키워드를 붙여 인터페이스 내에 default method를 선언할 수 있고 아래와 같이 사용할 수 있다.

    @AllArgsConstructor
    public class ImplClass impleents ExampleInterface {
    
        String name;
        
        @Override
        public void printName() {
        	System.out.println(this.name);
        }
        
        @Override
        public String getName() {
        	return this.name;
        }
    }
    
    
    public class App {
    
        public static void main(String[] args) {
            ImplClass example = new ImplClass("ratel");
            example.printNameUpperCase();
            // 출력: RATEL
        }
    }

     

     

    default method 사용 시 주의점

    1. default method로 선언된 기능은 모든 인스턴스들에게 제공되는 기능인데 해당 기능이 항상 제대로 동작할 것이라는 보장은 없다는 것이다.

    위 코드에서 예를 들면 getName()을 통해 어떠한 값이 올지 모르는 상황이고 null이 온다면 NullPointerException이 올 수 있는 상황이다.

    이러한 상황을 방지하기 위해 제안하는 방법은 @implSpec을 사용하여 문서화를 잘 해놓는 것(해당 메서드가 어떠한 기능을 하는지)이다.

    public interface ExampleInterface {
    
        void printName();
    
        String getName();
        
        /**
        * @implSpec 해당 메서드는 getName()으로 가져온 문자열을 대문자로 바꿔 출력하는 기능을 수행.
        */
        default void printNameUpperCase() {
        	System.out.println(getName().toUpperCase());
        };
    
    }

    다른 방법으로는 default method로 선언된 메서드를 구현체에서 재정의하여 사용하는 것이다.

    @AllArgsConstructor
    public class ImplClass implements ExampleInterface {
    
        String name;
        
        @Override
        public void printName() {
        	System.out.println(this.name);
        }
        
        @Override
        public String getName() {
        	return this.name;
        }
        
        @Override
        public void printNameUpperCase() {
        	System.out.println(this.name.toUpperCase());
        }
    }

     

     

    2. Object에서 제공하는 기능(equals, hasCode 등)은 default method로 제공할 수 없다.

    인터페이스에 Object에서 제공하는 기능을 default method로 선언한다면 컴파일 에러가 발생한다.

    재정의하고 싶다면 구현체에서 Override하여 재정의하면 된다.(모든 구현체는 기본적으로 Object를 implement하기 때문에)

     

     

    3. 인터페이스를 상속받는 인터페이스에서 다시 추상 메서드로 변경할 수 있다.

    printNameUpperCase()라는 default method를 가지고 있는 인터페이스를 상속받은 Bar 인터페이스에서는 해당 기능을 default mehtod로 제공하고 싶지 않다면 아래와 같이 추상 메서드로 선언하면 된다.

    public interface Bar extends ExampleInterface {
    
        void printNameUpperCase();
    }

    만약 추상 메서드로 선언하지 않는다면 Bar를 구현하는 구현체 인스턴스들은 모두 ExampleInterface 인터페이스에 선언된 printNameUpperCase 기능을 사용할 수 있다.

     

     

    4. 구현한 인터페이스들에 같은 default method가 존재한다면 재정의해주어야한다.

    하나의 구현체에서 2개의 인터페이스를 구현하고 있고 2개의 인터페이스에 같은 default method가 존재한다고 생각해보자.

    그러한 경우에는 어떠한 default method를 써야하는지에 대한 규칙이 없어 컴파일러가 모르기 때문에 컴파일에러가 발생하고 아래와 같이 구현체에서 해당 메서드를 재정의 해주어야한다.

    public interface Bar {
    
        default void printNameUpperCase() {
            System.out.println("Bar");
        };
    }
    
    public interface Qook {
    
        default void printNameUpperCase() {
            System.out.println("Qook");
        };
    }
    
    public class ImplClass implements Qook, Bar {
    	
        @Override
        void printNameUpperCase() {
            System.out.prinln("재정의해주기");
        }
    }

     

     


    <참고자료>

    - https://www.inflearn.com/course/the-java-java8/dashboard

    - https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

    댓글

Designed by Tistory.