Item 64. 객체는 인터페이스를 사용해 참조하라

서론

Item 51에 제네릭 타입으로 클래스가 아니라 인터페이스를 사용하라고 설명 되어있다.
이 말의 의미는 객체는 클래스가 아닌 인터페이스로 참조하라 라는 의미로 확장할 수 있다.
적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언해야 한다.

유연한 프로그램을 생성하는 인터페이스 타입 변수

1
2
3
4
5
//좋은 예
Set<Son> sonSet = new LinkedHashSet<>();

//나쁜 예
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

  • 좋은 예는 Set 인터페이스 타입으로 변수를 선언하였다.
  • 나쁜 예는 LinkedHashSet 타입으로 변수를 선언하였다.

만약 LinkedHashSet을 TreeSet으로 변환해야 한다면 어떨까?

1
2
3
4
5
//좋은 예
Set<Son> sonSet = new TreeSet<>();

//나쁜 예
LinkedHashSet<Son> sonSet = new TreeSet<>(); //컴파일 에러!

  • 위의 좋은 예는 Set 인터페이스 타입이기 때문에 컴파일 타임/런타임에 전혀 문제가 되지 않는다.
  • 나쁜 예는 LinkedHashSet 타입이기 때문에 TreeSet을 LinkedHashSet로 형변환 할 수 없다고 에러가 발생한다.

인터페이스 타입의 장점

  • 인터페이스 타입을 사용하면 클라이언트 코드를 수정하지 않고도 참조 객체를 변경할 수 있다.
  • 다른 타입의 객체를 사용하더라도 컴파일에러/런타임에러에 대한 걱정을 하지 않아도 된다.

인터페이스 타입의 단점

  • 인터페이스 타입에 선언된 메서드를 구현한 메서드만 사용이 가능하다.
  • 특정 구현체의 내부 메서드를 사용할 수 없다.

클래스를 참조해야 하는 경우

값 타입에는 클래스를 참조하라

  • String, Integer, Long과 같이 값 타입에 대해서는 인터페이스를 사용할 수 없으니 클래스를 참조해야 한다.
  • 또한 Integer, Long과 같은 타입을 사용할 때는 Number와 같은 상위 타입을 사용하지 말아야 한다.
    형변환이 발생 할 때 특정 데이터가 절삭되어 다른 결과가 발생 할 수 있기 때문이다.
  • 이런 경우에는 인터페이스나 상위타입 보다는 본래의 클래스로 참조하는 것이 좋다.

인터페이스에는 없는 메서드를 사용할 때에는 클래스를 참조해라

  • PriorityQueue 클래스에는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.
  • 클래스 타입을 직접 사용하는 경우에는 추가 메서드를 사용해야 하는 경우로 최소화 하는 것이 좋다.

정리

  • 인터페이스나 추상클래스를 구현하는 클래스를 사용할 때는 참조 변수를 인터페이스나 추상클래스로 사용하자
  • 인터페이스나 추상클래스 타입으로 참조 변수를 사용하면 참조 객체를 바꿔도 안전하다
  • 값 타입을 사용하는 경우에는 클래스 타입의 참조변수를 사용해야 한다.
  • 인터페이스에는 없는 메서드를 사용할 때는 클래스 타입의 참조변수를 사용해야 한다.
  • 적합한 인터페이스가 없다면 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인 상위 타입을 사용해야 한다.

참고

  • Effective Java 3rd Edition - Item 64. 객체는 인터페이스를 사용해 참조하라