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