[EffectiveJava] item 64. 객체는 클래스가 아닌 인터페이스로 참조하라

2025. 7. 1. 20:12·Backend/Java

💡객체는 클래스가 아닌 인터페이스로 참조하라

  • 이 원칙을 따르면 코드의 유연성이 증가하고, 유지보수가 쉬워지며, 구현 교체가 간편해진다.
  • 적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라
  • 적합한 인터페이스가 없다면 클래스의 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의) 클래스를 타입으로 사용하자

왜 인터페이스로 참조해야 할까?

1. 구현 교체가 쉽다

// 좋은 예: 인터페이스를 타입으로 사용
Set<Son> sonSet = new LinkedHashSet<>();

나중에 구현체를 변경할 때, 아래처럼 생성자만 바꾸면 된다.
Set<Son> sonSet = new HashSet<>();

하지만 실제 클래스(LinkedHashSet)로 사용하면

// 나쁜 예: 클래스를 타입으로 사용
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

2. 클라이언트 코드가 보호된다

List<String> list = new ArrayList<>();
list.trimToSize(); // ❌ 컴파일 에러 (List에는 없음!)

👉 ArrayList에는 trimToSize()가 있지만, List에는 없으므로 인터페이스 타입으로 참조하면 사용할 수 없다.

👉 즉, 클라이언트 코드가 특정 구현체의 기능에 의존하지 않도록 보호된다.


💡적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라

  • 변수 선언할 때 인터페이스를 사용
// ✅ 좋은 예: 인터페이스로 선언
List<String> list = new ArrayList<>();
Set<Integer> numbers = new HashSet<>();
Map<String, Integer> map = new HashMap<>();

-->

List<String> list = new LinkedList<>();
Set<Integer> numbers = new TreeSet<>();
Map<String, Integer> map = new LinkedHashMap<>();
  • 매개변수 타입으로 인터페이스를 사용
// ✅ 좋은 예: 인터페이스 타입을 사용
public void processList(List<String> list) {
    for (String item : list) {
        System.out.println(item);
    }
}

List<String> arrayList = new ArrayList<>();
processList(arrayList);  // 가능

List<String> linkedList = new LinkedList<>();
processList(linkedList); // 가능
  • 반환값 타입으로 인터페이스를 사용하라
// ✅ 좋은 예: 인터페이스를 반환 타입으로 사용
public List<String> getNames() {
    return new ArrayList<>(); // 또는 new LinkedList<>();
}
  • 필드 타입으로 인터페이스를 사용하라
// ✅ 좋은 예: 필드 타입을 인터페이스로 선언
class UserRepository {
    private List<String> users = new ArrayList<>();
}

2. 하지만 언제나 인터페이스를 써야 하는 건 아니다

예외 1) 값 클래스 (Value Class)

  • String, BigInteger 같은 하나의 구현체만 존재하는 클래스는 굳이 인터페이스로 참조할 필요가 없다.
  • 대부분 final이며, 인터페이스가 따로 존재하지 않는다.
String name = "Alice"; // ✅ 인터페이스가 없어도 문제 없음!
BigInteger bigNumber = new BigInteger("123456789");

예외 2) 클래스 기반 프레임워크의 객체

이런 경우라도 가능한 특정 구현 클래스보다는 보통 추상 클래스인 기반 클래스를 참조하는게 좋다.

ex) OutputStream(abstract class)를 포함한 java.io 패키지의 여러 클래스

OutputStream os = new FileOutputStream("file.txt");

예외 3) 인터페이스에는 없는 추가 기능을 사용할 때

클래스 타입을 직접 사용하는 경우는 추가 메소드를 꼭 사용해야 하는 경우로 최소화 해야하고, 남발해서는 안된다.

  • 예: PriorityQueue는 Queue 인터페이스에 없는 comparator() 메서드를 제공한다.
  • Queue 타입으로 선언하면 PriorityQueue의 추가 기능을 사용할 수 없다.
Queue<Integer> pq = new PriorityQueue<>();
pq.comparator(); // ❌ 컴파일 에러! (Queue에는 없음)

 

👉 이럴 땐 PriorityQueue 타입으로 참조하는 것이 적절할 수도 있다.

'Backend > Java' 카테고리의 다른 글

[Java] 정렬 알고리즘 정리  (1) 2025.07.02
[EffectiveJava] Item 14. Comparable을 구현할지 고려하라  (1) 2025.07.01
[EffectiveJava] Item 55. 옵셔널 반환은 신중히 하라  (1) 2025.07.01
[EffectiveJava] item 61. 박싱된 기본 타입보다는 기본 타입을 사용하라  (0) 2025.07.01
[EffectiveJava] Item 7. 다 쓴 객체 참조를 해제하라  (0) 2025.07.01
'Backend/Java' 카테고리의 다른 글
  • [EffectiveJava] Item 14. Comparable을 구현할지 고려하라
  • [EffectiveJava] Item 55. 옵셔널 반환은 신중히 하라
  • [EffectiveJava] item 61. 박싱된 기본 타입보다는 기본 타입을 사용하라
  • [EffectiveJava] Item 7. 다 쓴 객체 참조를 해제하라
devoks
devoks
꾸준히 작성해보자!
  • devoks
    ok's 개발 블로그
    devoks
  • 전체
    오늘
    어제
    • 분류 전체보기 (112) N
      • Backend (15)
        • SpringBoot (0)
        • Java (15)
      • Cs (18) N
      • Infra (0)
        • AWS (0)
        • Docker (0)
      • CodingTest (79)
        • Programmers (79)
  • 링크

    • My GitHub
  • 인기 글

  • 태그

    java
    CS
    BufferedReader
    programmers
    effectivejava
    StringTokenizer
    switch
    json
    codingtest
    BufferedWriter
  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devoks
[EffectiveJava] item 64. 객체는 클래스가 아닌 인터페이스로 참조하라
상단으로

티스토리툴바