www.youtube.com/watch?v=HLnMuEZpDwU
목표
자바의 인터페이스에 대해 학습하세요.
학습할 것 (필수)
- 인터페이스 정의하는 방법
- 인터페이스 구현하는 방법
- 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
- 인터페이스 상속
- 인터페이스의 기본 메소드 (Default Method), 자바 8
- 인터페이스의 static 메소드, 자바 8
- 인터페이스의 private 메소드, 자바 9
추천 도서
인터페이스란?
먼저 목차의 내용을 정리하기 전에 간단하게 인터페이스란 무엇인지 정리해보겠습니다.
- 인터페이스란 추상 메서드, 상수만을 선언 가능.
- 다른 클래스에서 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 처럼 사용할 수 있습니다.
추천하지 않는 이유
- 사용하지 않는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야합니다.
- 컴파일할 때에는 사용되겠지만 런타임에는 용도가 필요없다. Maker Interface는 런타임에 사용할 목적이 있으므로 다름
- Binary Code Compatibility (이진 호환성)을 필요로 하는 프로그램일 경우, 새로운 라이브러리를 연결하더라도, 상수 인터페이스는 프로그램이 종료되기 전까지 이진 호환성을 보장하기 위해 계속 유지되어야 한다.
- IDE가 없으면, 상수 인터페이스를 Implement 한 클래스에서는 상수를 사용할 때 네임스페이스를 사용하지 않으므로, 해당 상수의 출처를 쉽게 알 수 없다. 또한 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스도 인터페이스의 상수들로 오염된다.
- 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위이다. 따라서 상수 인터페이스를 구현한다는 사실은 클라이언트에게는 중요한 정보가 아니다. 다만, 클라이언트들을 혼동시킬 뿐이다.
- 상수 인터페이스를 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
'Study > java' 카테고리의 다른 글
[백기선님의 자바 라이브 스터디] 10주차 - 멀티쓰레드 프로그래밍 (0) | 2021.03.02 |
---|---|
[백기선님의 자바 라이브 스터디] 9주차 - 예외 처리 (0) | 2021.02.24 |
[백기선님의 자바 라이브 스터디] 7주차 - 패키지 (0) | 2021.02.19 |
[백기선님의 자바 라이브 스터디] 6주차 - 상속 (0) | 2021.02.17 |
[도서] Object - 객체 설계 (0) | 2021.01.05 |