ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java-30] java8 과 java9 의 인터페이스 변화
    카테고리 없음 2021. 1. 7. 02:00

    Java 8,9 interface 의 default, static, private 메소드

    자바8 이상 interface

    일반적으로 우리가 사용하는 자바에서는 인터페이스와 메소드는 는 밀접한 관계를 가지고 있다. 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메소드를 구현을 하거나, 슈퍼클래스의 구현을 상속받아야 한다.

    interface의 문제점

    무법지대와 같은 개발 세계에서, 설계가 수정되는일이 비일비재(非一非再) 하다 . Designer(설계자)새로운 메소드를 추가 하거나, 인터페이스를 바꾸고싶을 때, 인터페이스를 바꾸면 이전에 해당 인터페이스를 구현했던 모든 클래스의 구현도 고쳐야 한다.

    interface의 개선

    자바 8에서는 인터페이스를 구현할 수 있는 정적 메서드(static method), 디폴드 메소드(default method)를 지원 한다. 즉, 자바 8 이상부터는 인터페이스 안에서 추상 메소드 정의뿐만 아니라 구현 또한 가능하다는 것이다. 이점으로는 기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메소드의 디폴트 메소드를 상속 받게 된다. 이렇게 하면, 기존의 코드 구현을 바꾸도록 강요하지 않으면서도 인터페이스를 변경이 가능하다.

    default 메소드

    자바 8 이상에서는 앞서 말한 자바8이전에 interface를 통해서 구현부를 상속받은 클래스에 작성할 필요가 없어졌다. 간단하게 예제를 들어도록 하자.

    public interface Foo {
        String getName(); // 이름을 return 하는 추상 메소드
    
        default void printName(){ // 이름을 출력하는 default 메소드
            System.out.println(getName());
        }
    }
    public class Main implements Foo{
        String name;
    
        public Main(String name){
            this.name = name;
        }
    
        @Override // getName() 이름을 리턴하는 추상메소드 
        public String getName(){
            return this.name;    
        }
    }
    
    public static void main(String args[]){
        Foo foo = new Main("Lee-Maru");
        foo.printName(); // Lee-Maru 출력
    }
    • 좀더 자세히 알아보도록 하자 우리는 2개의 java 파일을 가지고 있다고 가정해보다, 인터페이스에서 number 라는 변수가 0보다 큰지 작은지에 대해서 확인할 수 있는지 구분하는 메소드 이다.

    Interface

    public interface Foo {
    
        Integer getNumber();
    
        // default 메소드 구현체
        default void compareZero(){
            if(getNumber() > 0){
                System.out.println("more than Zero ");
            }else{
                System.out.println("less than Zero ");
            }
        }
    
    }

    Class 코드

    public class Main implements Foo{
    
        Integer number;
    
        public Main(Integer number){
            this.number = number;
        }
        public Main() {
        }
        @Override
        public Integer getNumber() {
            return this.number;
        }
    }

    실행 코드

    public class App {
        public static void main(String[] args) {
            Foo foo = new Main(3);
            foo.compareZero(); //more than Zero
    
            Foo foo1 = new Main(); // Nullpoint Exceoption
            foo1.compareZero();
        }
    }
    • 앞에 foo 라는 인스턴스는 생성자에 3이라는 멤버변수를 초기화 시키면서 생성되었다. 그래서 compareZero() 라는 메소드를 실행해도 정상적으로 실행된다.
    • 하지만 number 변수가 null 이 되도록 하는, 생성자 (기본 생성자) 를 생성해서 작성하게 되면 어떻게될까? NullPointer Exceoptio 에러가 발생한다.
    • 이런 인터페이스를 통해 API를 개발을 하고, 사용하게 된다면 우리는 한가지 의심해야할 것이 있다.

    API는 전부 믿을 수 없다.

    물론 우리가 일반적으로 사용하는 API 는 구글, 네이버 에 많은 사람들이 사용했기 때문에, API를 사용하는데 있어서 크게 어려움이 없을 수도 있다. 하지만 그렇지 않은 API 예를들어 회사 내부에서 작성한 API 코드 이던지, 많은 사람들이 사용해보지 않은 API에서는 사용하는 사람은 문서를 잘 읽어야 하며, 만드는 사람은 문서화를 해야한다.

    문서화 & @implSpec

    • API 를 문서화 하는건 어렵지 않다. 어느 부분에서 에러가 발생할 수 있으며, 또는 어떻게 사용해야 할지 알려줘야 하며, return 값은 무엇인지 최소한의 정보를 적어 두면 된다.
        /**
         * Returns the number of elements in this list.  If this list contains
         * more than {@code Integer.MAX_VALUE} elements, returns
         * {@code Integer.MAX_VALUE}.
         *
         * @return the number of elements in this list
         */
        int size();

    본문은 List 인터페이스의 size() 메소드의 문서를 발췌한 것이다. 이게 우리가 흔히 볼 수 있는 문서화이다. 문서화의 이유는 제대로 동작할 수 있는 API 인지 모르기 때문이다.

    문서화 태그 종류

    태그 내용
    @param 메서드의 파라미터에 대한 정보를 담는다. 매개변수가 듯하는 값을 명사구로 사용한다.
    @return 메서드의 반환 타입이 void 가 아니라면 반환 타입을 명시, 반환값이 뜻하는 값을 명사구로 작성, 드물게는 산술 표현식으로도 작성
    @throws 발생 가능성이 있는 모든 예외에 대해 명시한다. if 로 시작해 해당 예외를 던지는 조건을 설명하는 절이 뒤따름.
    @implSpec 메소드에 대한 정보를 문서에 남겨, 다른 프로그래머에게 그 메서드를 올버라게 재정의 하는 방법을 알려줘야함.
    • 우리는 implSpec 을 사용해보도록 하자.
        /**
         * @throws NullPointerException 이 발생할 수 있습니다. 
         * @implSpec 이 메소드는 null 에 취약 하오니, 인스턴스에 null 발생 시,
         * 메소드 재정의 요망
         */
        default void compareZero(){
            if(getNumber() > 0){
                System.out.println("more than Zero ");
            }else{
                System.out.println("less than Zero ");
            }
        }
    • 보다 사용하는 사람으로 하여금, 어떤 문제가 발생할 수 있는지 @throws NPE 에 대해서 명시했고, @impleSpec 으로 통해 어떤 상황에서 재정의를 해야할지 간단하게 적어보았다.

    default method 재정의

    • 이런 상황에서 우리는 default 메소드를 재정의할 필요가 있다. 그럼 default 메소드의 재정의는 어디서 진행하는게 좋을까?
    public class Main implements Foo{
        @Override
        public void compareZero(){
            if(getNumber() == null){
                System.out.println("number 에 null이 들어가 있습니다.");
            }else{
                if(getNumber() > 0){
                    System.out.println("more than Zero ");
                }else{
                    System.out.println("less than Zero ");
                }
            }
        }
    }
    • 이런식으로 상속받은 클래스에서 직접 오버라이딩이 진행된다.

    default method의 다중상속

    • 이쯤에서 궁금할 수 있다. class는 한꺼번에 여러개의 interface 를 상속받을 수 있다. 그러면, 똑같은 메소드의 default method 의 다중상속은 어떻게 진행이될까?

    예를 들어보도록 하자

    public interface Boo {
        default void printName(){
            System.out.println("Hello");
        }
    }
    
    public interface Foo {
        default void printName(){
            System.out.println("Lee-Maru");
        }
    }
    
    public class Main implements Foo, Boo{ // Main 클래스는 Boo 와 Foo 중 어떤 printName() 을 사용해야 할지 모른다. 
        @Override
        public void printName() {
            System.out.println("Seokmin"); // 무조건 오버라이딩을 진행해 주도록 한다. 
        }
    }
    
    >> Error : Main inherits unrelated defaults for printName() from types org.example.test2.Foo and org.example.test2.Boo

    사실 자바에서는 이 2개의 메소드 중에 뭘 사용해야할지 도저히 모른다. 그렇기 때문에, 만약 이 2개의 interface 를 동시 상속 받고 싶다면 *오버라이드 해서 사용하면 된다. *

    default method의 Object 메소드 오버라이딩

    • 한가지 주의해야 할점은 우리가 흔히 사용하는 ** eqauls, hashcode 와 같은 메소드 들은 재정의 할 수 없다. 한번 해보도록 해보자
    public interface Boo {
        default boolean equals(Object o){
            return true;
        }
        default int hashCode(){
            int result = 0;
            return result;
        }
    }
    
    >> Error : Default method 'equals' overrides a member of 'java.lang.Object'
    • 이를 오버라이딩 하고 싶다면 클래스를 통해서 진행 해 두어야 한다.

    static 메소드

    • 우리가 지금까지 봐왔던 기본 메서드 default method 는 인스턴스에서 사용하는 메소드인 반면에, static 메서드는 레퍼런스 타입에 직접 붙힐 수 있다. 여타 스태틱한 메서드와 동일하다고 보면 된다.

    static 메소드는 구현부에서 오버라이딩 할 수 없다.

    public interface Foo{
        static void printHello(){            
            System.out.println("Hello"); // 스테틱 인터페이스 메소드 구현
        }
    }
    
    public class Main implements Foo{
        @Override
        public void printHello(){
            System.out.println("Bye"); 
            //Error -> Method does not override method from its superclass
        }
    }

    디폴트 메서드를 사용하면서 알고있어야할 3가지 규칙

    • default 나 static 을 갖는 메서드를 상속받을 때, 우리가 지켜야할 또는 알고있어야할 규칙을 알고 있어야 한다.

    1. 클래스가 항상 이긴다. 클래스나 슈퍼클래스에서 정의한 메서드가 디폴트 메서드 보다 우선권을 가지게 된다.

    2. 1번 규칙 이외의 상황에서는 서브인터페이스가 이긴다. 상속 관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브인터페이스가 이긴다. 즉, B가 A를 상속받는다면, B가 A를 이긴다.

    3. 여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출하자

    java9 private

    • default , static 메서드를 자바 8을 통해서 공개를 했지만, 여전히 편하다? 라고 하기에는 힘들다.

    default, static 문제점

    • interface 내부 메서드에서 사용할 분인데, public method로 만들어야 하는 문제가 있었고,
    public interface Boo {
        /**
         * @return "Bye"
         */
        default String printBye(){ // interface 내부 메소드 knockDoor 메소드에서 사용
            return "Bye";
        }
        default void knockDoor(){
            System.out.println("Ok.. " + printBye());
        }
    }
    public static void main(String[] args) {
            Boo boo = new Main();
            boo.printBye(); // Boo 인터페이스의 내부 메소드 인데, 호출이 가능함. 
            boo.knockDoor();
        }
    • interface를 구현할 수 있는 interface 또는 class가 특정 metod에 접근하는거 자체를 막고싶을 수도 있다.
    public class Main implements Boo{
        @Override
        public String printBye(){ //상속받은 메서드를 원하지 않게 오버라이딩 한다. 
            return "Bye";
        }
    }
    • 그래서 우리는 private 메서드 사용을 통해서, 다음과 같은 문제점을 막을 수 있다.

    private 사용 예제

    
    public interface Boo {
        /**
         * @implSpec 이 메소드는 오버라이드 하거나, 외부에서 접근할 수 없습니다.  
         * @return "Bye"
         */
        private String printBye(){
            return "Bye";
        }
        default void knockDoor(){
            System.out.println("Ok.. " + printBye());
        }
    }

    다음과 같이 private String printBye(){ //code } 를 작성함을 통해서 외부에서 접근이 불가능하도록 만들어 두었는데, 이렇게 private 메서드를 통해서, 메서드의 제한을 둘 수 있다.

    public class Main implements Boo{
        @Override //Error -> method does not override or implement a method from a supertype
        public String printBye(){
            return "Bye";
        }
    }

    댓글

Designed by Tistory.