Study/java

[백기선님의 자바 라이브 스터디] 8주차 - 인터페이스

에디개발자 2021. 2. 21. 23:48
반응형

www.youtube.com/watch?v=HLnMuEZpDwU 

나를 닮았다고 한다...

목표

자바의 인터페이스에 대해 학습하세요.

학습할 것 (필수)

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

추천 도서

book.interpark.com/product/BookDisplay.do?_method=detail&sc.prdNo=220675219&gclid=Cj0KCQiApsiBBhCKARIsAN8o_4gBdWvcLgQ7MkppWwNVd8BU1dELce8QtUxvrL9jHijO2gOC9yVTPGEaAiyDEALw_wcB

 

싸니까 믿으니까 인터파크도서

직장인을 위한 비폭력대화(NVC) 실용 가이드 행복하고 효율적이며 스트레스가 적은 직장생활을 위한 제안소통과 공감의 대화를 위하여 직장생활을 하다 보면 어렵고 힘든 상황에 직면하는 경우

book.interpark.com

 

인터페이스란?

먼저 목차의 내용을 정리하기 전에 간단하게 인터페이스란 무엇인지 정리해보겠습니다.

 

  • 인터페이스란 추상 메서드, 상수만을 선언 가능.
  • 다른 클래스에서 implements하여 모든 메서드 로직을 강제 구현 시킴.
  • 자바는 다중 상속 ( extends ) 가 불가능하여 인터페이스를 도입.

 

interface vs abstract

가장 큰 차이점은 추상화 메서드 강제성입니다.

interface는 모든 메서드가 추상화 메서드이고 abastract 클래스는 일부만 추상화 메서드로 선언할 수 있습니다.

tip! 면접질문으로 아주 많이 나옵니다!!

 

인터페이스 정의하는 방법

인터페이스는 클래스 파일과 달리 interface를 선언합니다.

public interface Policy {
    
}

 

Java 8 이전에는 추상 메서드와 상수만을 사용하여 implements한 클래스에게 강제 구현을 하였습니다. 하지만 Java 8 이후부터는 default 라는 메서드를 구현할 수 있게 되어 좀 더 유연한 interface 설계가 가능해졌습니다.

public interface Policy {
    String POLICY_NAME = "policyName";   // java8 이전

    void apply();                        // java8 이전

    default void commonApply() {         // java8 이후
        System.out.println("apply common policy");
    }
    
    static void staticCommonApply() {    // java8 이후
        System.out.println("apply static common policy");
    }
}

 

 

이렇게 구현하면 implements한 클래스에서는 commonApply 메서드를 호출할 수 있습니다.

public class StorePolicy implements Policy {
    @Override
    public void apply() {
        System.out.println(POLICY_NAME);
        // logic

        Policy.staticCommonApply();
        commonApply();    // call interface default method 
    }
}

 

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

Policy interface와 StorePolicy N개를 통해 예제를 살펴보겠습니다.

public interface Policy {
    void apply();
}

 

아래와 같은 Store1Policy가 3개가 있다고 가정합니다.

public class Store1Policy implements Policy {
    @Override
    public void apply() {
        System.out.println("store1");
    }
}

 

StorePolicyFactory 클래스에서 Policy 객체를 가져옵니다.

public class StorePolicyFactory {
    public Policy getInstance(int type) {
        Policy policy = null;
        switch (type) {
            case 1:
                policy = new Store1Policy();
                break;
            case 2:
                policy = new Store2Policy();
                break;
            case 3:
                policy = new Store3Policy();
                break;
            default:
                policy = null;
        }
        return policy;
    }
}

 

그럼 Store 클래스에서 type만 변경하여 내가 원하는 객체를 얻어올 수 있습니다.

public class Store {
    private StorePolicyFactory storePolicyFactory;
    private Policy policy;

    public Store() {
        storePolicyFactory = new StorePolicyFactory();
    }
    
    public void setPolicyInstance(int type) {  // type에 따라 다른 객체를 가져옵니다
        policy = storePolicyFactory.getInstance(type);
    }

    public void print() {
        policy.apply();
    }
}

 

인터페이스 상속

인터페이스는 추상화 클래스와 다르게 다중 상속이 가능합니다. 

public class Store1Policy implements Policy, Policy1, Policy2 {
    @Override
    public void apply() {    // call method in Policy interface

    }
    
    @Override
    public void apply1() {    // call method in Policy1 interface

    }
    
    @Override
    public void apply2() {    // call method in Policy2 interface

    }
}

 

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

java 8로 넘어오면서 interface에 Default Method를 선언할 수 있게되었습니다. Default Method는 클래스 상속 설계 중 비효율적이었던 부분을 고칠 수 있게 되었습니다.

 

하나의 예시를 들어보겠습니다. 

  • SeoulStore 객체가 있습니다. 이 객체는 StorePolicy 중 amountPolicy만 쓰고 싶습니다.
  • StorePolicy는 amountPolicy, countPolicy, addressPolicy가 있습니다.
public interface StorePolicy {
    void amountPolicy();
    void countPolicy();
    void AddressPolicy();
}
public class SeoulStore implements StorePolicy {
    @Override
    public void amountPolicy() {
        // 써야한다.
    }

    @Override
    public void countPolicy() {
        // 안쓰고 싶은데..
    }

    @Override
    public void AddressPolicy() {
        // 안쓰고 싶은데..
    }
}

이처럼 안쓰는 메서드지만 추상화 클래스이기 때문에 오버라이드하여 빈 블록을 선언했어야만 했습니다. 하지만 Defualt Method를 통해서 이러한 선언을 피할 수 있습니다.

 

public interface StorePolicy {
    void amountPolicy();  // 모든 Store에서 적용되야하는 정책
    default void countPolicy() {};   // Dynamic하게 적용
    default void AddressPolicy() {}; // Dynamic하게 적용
}
public class SeoulStore implements StorePolicy {
    @Override
    public void amountPolicy() {
        // 써야한다.
    }

    // 이하 로직 Not 필수
//    @Override
//    public void countPolicy() {
        // 안쓰고 싶은데..
//    }

//    @Override
//    public void AddressPolicy() {
        // 안쓰고 싶은데..
//    }
}

 

default method 끼리 충돌이 발생하는 경우

위와 같은 조건에 StaffPolicy가 추가된다고 가정해봅니다. StaffPolicy도 StorePolicy와 동일하게 구성되어있습니다. 

그럼 SeoulStore는 StorePolicy, StaffPolicy 두 개를 implements 받습니다. 이 경우 어떻게 해야하는지 알아보겠습니다.

public interface StaffPolicy {
    void amountPolicy();
    default void countPolicy() {};
    default void AddressPolicy() {};
}
public class SeoulStore implements StorePolicy, StaffPolicy {
    @Override
    public void amountPolicy() {
        // 써야한다.
    }
 
    // 이하 메서드는 default 충돌 메서드들
    @Override
    public void countPolicy() {
        StorePolicy.super.countPolicy();  // StorePolicy class 메서드 호출
    }

    @Override
    public void AddressPolicy() {
        StaffPolicy.super.countPolicy();  // StaffPolicy class 메서드 호출
    }
}    

위 코드와 같이 동일한 Default Method를 선언한 경우 오버라이드하여 선언해줘야합니다. 그리고 super 키워드를 이용하여 메서드를 선택 호출할 수 있습니다. 물론 새로 정의도 가능합니다. 

 

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

인터페이스의 static 메서드는 위에서 선언한 일반적인 메서드와 다르게 오버라이드가 불가능한 메서드입니다. 또한 호출 방법도 다르게 {Interface명}.{static메서드명}으로 호출해야합니다.

public interface StorePolicy {
    static void staticPolicy() {};
}
public class SeoulStore {
    public void staticPolicy() {
        StorePolicy.staticPolicy();  // call interface static method
    }
}

 

 

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

Java8버전 이후부터 default, static 메서드 기능이 추가되었지만 아직 불편한 점이 있습니다. interface는 특성상 모든 메서드가 공개되어 있습니다. 이 것은 프라이빗한 코드를 작성하지 못하게 되고 개발자들은 모든 메서드에 접근할 수 있는 권한이 생기기 때문에 캡슐화가 불가능합니다. 이러한 단점을 개선하기 위해 Java 9 부터 private method 기능이 추가되었습니다. ( static 메서드에도 사용가능 )

public interface StorePolicy {

    void amountPolicy();

    default void countPolicy() {};

    default void AddressPolicy() {
        print();
    };

    static void staticPolicy() {};
    
    private void print() {
        System.out.println("store policy print");
    }
}

 

Constant Interface

Constant Interface는 안티 패턴입니다.

 

interface에서 상수를 선언하면 자동으로 public static이 붙게 됩니다. 즉 interface에 변수를 선언하면 모두 노출하게되어 constant 처럼 사용할 수 있습니다.

 

추천하지 않는 이유

  1. 사용하지 않는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야합니다.
  2. 컴파일할 때에는 사용되겠지만 런타임에는 용도가 필요없다. Maker Interface는 런타임에 사용할 목적이 있으므로 다름
  3. Binary Code Compatibility (이진 호환성)을 필요로 하는 프로그램일 경우, 새로운 라이브러리를 연결하더라도, 상수 인터페이스는 프로그램이 종료되기 전까지 이진 호환성을 보장하기 위해 계속 유지되어야 한다.
  4. IDE가 없으면, 상수 인터페이스를 Implement 한 클래스에서는 상수를 사용할 때 네임스페이스를 사용하지 않으므로, 해당 상수의 출처를 쉽게 알 수 없다. 또한 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스도 인터페이스의 상수들로 오염된다.
  5. 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위이다. 따라서 상수 인터페이스를 구현한다는 사실은 클라이언트에게는 중요한 정보가 아니다. 다만, 클라이언트들을 혼동시킬 뿐이다.
  6. 상수 인터페이스를 Implement 한 클래스에 같은 상수를 가질 경우, 클래스에 정의한 상수가 사용되므로 사용자가 의도한 흐름으로 프로그램이 돌아가지 않을 수 있다. 아래는 간단한 예제이다.
public interface Constants { 
    public static final int CONSTANT = 1; 
} 

public class Class1 implements Constants { 
    public static final int CONSTANT = 2; // * 

    public static void main(String args[]) throws Exception { 
        System.out.println(CONSTANT); 
    } 
}

 

그럼 어떻게??

자바문서에서 Constant Interface를 Anti 패턴으로 명시하였고 이 방안으로 ​import static 구문​ 사용을 권장합니다. Constant Interface와 동일한 기능과 편리성을 제공합니다. 

public final class Constants { 
    private Constants() { 
        // restrict instantiation
    } 
    
    public static final double PI = 3.14159; 
    public static final double PLANCK_CONSTANT = 6.62606896e-34; 

} 
import static Constants.PLANCK_CONSTANT; 
import static Constants.PI; 

public class Calculations { 
    public double getReducedPlanckConstant() {
        return PLANCK_CONSTANT / (2 * PI); 
    } 
}

Reference

반응형