8주 차 시작!

 

인터페이스 정의하는 방법

 

인터페이스란?

인터페이스는 (구현한) 하위 인스턴스를 참조할 수 있는 타입이며, 해당 인스턴스가 어떠한 행위를 할 수 있는지 클라이언트에게 알려주는 일종의 계약서 역할을 한다.

클라이언트는 해당 행위를 알려주고 구현부는 해당 타입의 인스턴스에게 맡김으로써 정보 은닉을 지킬 수 있다. 이는 반대로 생각한다면 클라이언트는 구현부의 변경에 따른 여파가 없음을 의미한다.

 

 

인터페이스를 정의하는 방법?

{Access-Level-Modifier} interface {Name} {

        // JDK 7까지는 기본적으로 Static Method를 제외하고 추상 타입의 public Method 만을 
    // 선언할 수 있다.

        // 이 Method는 해당 Interface를 구현하는 Class에서 무조건 Overiding하여야 한다. 
        public String xxxMethod(String str);

        // Access-Level-Modifier 를 작성하지 않고 정의할 수 있으며, 기본 값은 Public이다.
        String xxxMethod(String str);

        // 상수 정의 
        // 인터페이스의 용도를 반하는 대표적인 안티패턴이다. 
        // 상수 필드를 정의하는 것은 해당 타입의 하위 구현체의 구현부를 노출하는 행위이다.
        // 클라이언트에게는 필요없는 정보이기도 하다.
        static final String name = "Lob!";

}

 

인터페이스 구현하는 방법

 

Interface(만) 를 instantiation 할 수 있을까?

public interface SampleInterface {

    String xxxMethod(String str);

}

----------

// error: SampleInterface is abstract; cannot be instantiated
SampleInterface sampleInterface = new SampleInterface();

Interface 만으로는 instantiation시킬 수 없으며, 해당 Interface를 Implement 한 Sub Class(Instance)를 통해서만 구현할 수 있다.

 

 

구현 방법 1 : 익명 클래스 방식

해당 방식은 일회성으로 사용되고, 재사용할 필요가 없는 경우에 이용할 수 있다.

SampleInterface sampleInterface = new SampleInterface() {
            @Override
            public String xxxMethod(String str) {
                return "Hello "+str;
            }
        };

        // lambda 방식 (JDK 8+)
        SampleInterface sampleInterfaceOfLambda = str -> "Hello "+str;

        System.out.println(sampleInterface.xxxMethod("lob"));
        System.out.println(sampleInterfaceOfLambda.xxxMethod("lob"));

 

구현 방법 2 : 인터페이스를 구현하는 방식

Interface의 Abstract Method를 Override한 뒤 생성하는 방식이다.

public class SampleInterfaceImpl implements SampleInterface {
    @Override
    public String xxxMethod(String str) {
        return "Hello "+str;
    }
}

----------

SampleInterface sampleInterfaceOfSubClass = new SampleInterfaceImpl();

System.out.println(sampleInterfaceOfSubClass.xxxMethod("lob"));

 

인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

Interface도 Abstract Class나 Super Class같이 Sub Class를 참조하는 타입으로써 사용할 수 있다.

클래스 상속 관계같이 인터페이스 타입으로 Casting할 경우 인터페이스에 정의된 Method만을 참조하여 사용할 수 있다.

public class SampleInterfaceImpl2 implements SampleInterface {

    public String someMethod(String str) {
        return "Sub Class Hello "+str;
    }

    @Override
    public String xxxMethod(String str) {
        return "Hello "+str;
    }
}

SampleInterface sampleInterface1 = (SampleInterface) new SampleInterfaceImpl2();

sampleInterface1.xxxMethod("lob");

// error: cannot find symbol sampleInterface1.someMethod("lob");
sampleInterface1.someMethod("lob");

// 이렇게 Down Casting 하면 사용 가능하다.
((SampleInterfaceImpl2) sampleInterface1).someMethod("lob");

 

객체는 인터페이스를 사용해 참조하자. (JDK 5+)

이펙티브 자바에서는 적합한 Interface가 존재하는 경우 매개변수뿐만 아니라 반환 값, 변수, 필드 변수에 대한 모든 타입을 Interface로 선언하라고 한다. 이는 하위 구현체들이 모두 Interface 타입으로 참조가 가능하기에, 좀 더 유연한 코드를 작성할 수 있음을 의미한다.

// 좋은 예
// 해당 경우에는 컬랙션 변수가 Param이라고 가정하였을 때 ArrayList, LinkedList, Stack, 
// Vector 등이 전달되어도 문제가 발생하지 않는다.
List<String> list = new ArrayList<>();

// 나쁜 예
// 해당 경우에는 구체적인 클래스 타입을 적용하였기에 다른 List 구현체들이 오게될 경우
// 에러가 발생한다.
ArrayList<String> list = new ArrayList<>();

물론 Class가 특별한 기능을 제공하고, 해당 기능을 사용하여야 하는 경우에는 그러지 않아도 된다.

 

적합한 Interface가 존재하지 않는다면, Class의 계층 구조 중 필요한 기능을 만족하고 추상적인 클래스를 타입으로 사용하자.

 

인터페이스 상속

Interface는 Interface 끼리만 상속 관계를 연결할 수 있으며, Interface를 이용하여 다중 상속(구현)을 지원할 수 있다.

public interface SampleInterface {

    String xxxMethod(String str);

}

public interface SampleInterfaceForDefault {

    default String xxxxMethod(String str) {
        return "Hello "+str;
    }
}

public class SampleInterfaceImpl3 implements SampleInterface, SampleInterfaceForDefault {
    @Override
    public String xxxMethod(String str) {
        return "override Hello! "+str;
    }
}

----------

SampleInterfaceImpl3 sampleInterface2 = new SampleInterfaceImpl3();

sampleInterface2.xxxMethod("lob");
sampleInterface2.xxxxMethod("lob");

Interface들을 다중 구현하였을 경우 동일한 메서드 시그니쳐가 존재한다면, Override하여 사용하여야 한다. (그렇지 않다면 컴파일 에러가 발생한다.)

 

Class와 Interface를 같이 상속, 구현하였을 경우에는 Class에 존재하는 메서드가 우선권을 가진다.

 

인터페이스의 기본 메소드 (Default Method), 자바 8

JDK 8 이후부터는 Interface에 Static Method를 제공하는 것 말고도 Default Method라는 것이 생기게 되었다. 이는 기존에 사용되던 Interface의 문제점인 한번 배포된 Interface는 수정이 어렵다.라는 것을 해결하기 위하여 추가되었다고 생각한다.

 

Default Method가 도입된 JDK 8 이후에는 많은 Method들이 추가되었음을 알 수 있다.

 

이미 배포된 Interface 에 메서드를 추가하게 된다면, 기존에 해당 Interface를 사용하던 모든 프로젝트에서는 개발 중일 때에는 컴파일 에러, 실행 중 인 것들에 대해서는 NoSuchMethod Error가 발생하게 된다.

 

메서드를 추가한다면?

public interface SampleInterface {

    String xxxMethod(String str);

    // 해당 메서드가 추가되었다.
    void addMethod(String str);

}

----------

// error: <anonymous interfaceexample.InterfaceClient$1> is not abstract and does not override abstract method addMethod(String) in SampleInterface
SampleInterface sampleInterface = new SampleInterface() {
            @Override
            public String xxxMethod(String str) {
                return "Hello "+str;
            }
        };

// Multiple non-overriding abstract methods found in interface interfaceexample.SampleInterface
// 구현 방식 1 = lambda 방식 (JDK 8+)
SampleInterface sampleInterfaceOfLambda = str -> "Hello "+str;

----------

//Class 'SampleInterfaceImpl3' must either be declared abstract or implement abstract method 'addMethod(String)' in 'SampleInterface'
public class SampleInterfaceImpl3 implements SampleInterface, SampleInterfaceForDefault {
    @Override
    public String xxxMethod(String str) {
        return "override Hello! "+str;
    }
}

이렇게 해당 Interface를 구현하는 모든 클래스, 인터페이스에서 문제가 발생한다.

public interface SampleInterface {

    String xxxMethod(String str);

    default void addMethod(String str) {
        System.out.println("Hello "+str);
    }

}

default 키워드를 사용하여 Method를 추가한 뒤 구현하면 해당 문제가 발생하지 않는다.

 

추가적으로 default Method도 Public으로 인식되며, Static Method와 달리 재정의가 가능하다.

이는 default Method도 Instance와 같이 가시되며, 런타임 시점에서 Dispatch가 되기 때문이다.

 

인터페이스의 static 메서드, 자바 8

Interface의 Static Method도 JDK 8 이후에 제공되기 시작하였으며, default Method와 같이 구현부를 가지게 된다.

다른 점은 위에서 이야기하였던

  • Override가 불가능한 것

  • Instance가 아닌 Interface와 가시 되며. 컴파일 시점에서 Dispatch 된다는 것

  • 그리고 일반적인 Static Method와 달리 상속되지 않고 Interface Type을 직접 참조하여 호출해야 한다는 것이다.

    이는 Interface를 다중 상속을 하였을 경우 발생할 수 있는 문제를 방지하는 조치인 것 같다.

public interface SampleInterface {

    String xxxMethod(String str);

    default void addMethod(String str) {
        System.out.println("Hello "+str);
    }

    static void addStaticMethod(String str) {
        System.out.println("Static Hello "+str);
    }

}

----------

SampleInterface.addStaticMethod("lob");

SampleInterface sampleInterface3 = new SampleInterface() {
            @Override
            public String xxxMethod(String str) {
                return "null";
            }


            // Method does not override method from its superclass
            *@Override*
            // Inner classes cannot have static declarations
            static void addStaticMethod(String str) {
                System.out.println("Static Hello "+str);
            }
        };

해당 메서드도 인터페이스를 구현하지 않고도 Util Method를 이용하고 싶다면 구현하는 방식으로 사용하면 좋을 것 같다.

 

인터페이스의 private 메서드, 자바 9

private Method가 추가된 이유로는 인터페이스의 static, default Method의 로직을 공통화하고 재사용하기 위함이다. 이는 JDK 8에 발생했던 중복 코드 문제를 해결하게 되었다.

 

private Method도 Static, default Method 같이 구현부를 가져야한다는 동일한 제약을 가진다.

 

간단한 예시 코드

 

JDK 8

default void multiplyAfterAddingNumbers(long num1, long num2) {
        long val = num1 + num2;
        val = val * val;
        System.out.println("result = " + val);
}

default void multiplyAfterSubtractingNumbers(long num1, long num2) {
    long val = num1 - num2;
    val = val * val;
    System.out.println("result = " + val);
}

 

JDK 9

default void multiplyAfterAddingNumbers(long num1, long num2) {
        long val = multiplyNumbers(num1 + num2);
        System.out.println("result = " + val);
    }

    default void multiplyAfterSubtractingNumbers(long num1, long num2) {
        long val = multiplyNumbers(num1 - num2);
        System.out.println("result = " + val);
    }

    private long multiplyNumbers(long val) {
        return val * val;
    }

 

참고 자료

  • 이펙티브 자바

'Live Study' 카테고리의 다른 글

Live Study_Week 11. Enum  (0) 2021.01.28
Live Study_Week 09. 예외 처리  (0) 2021.01.11
Live Study_Week 07. 패키지  (0) 2020.12.28
Live Study_Week 06. 상속  (0) 2020.12.21
Live Study_Week 05. 클래스  (0) 2020.12.15

+ Recent posts