← 학습기록 (Java · 이펙티브 자바 3E)

3장 - 모든 객체의 공통 메서드

10. equals는 일반 규약을 지켜 재정의하라

equals 메서드는 재정의하기 쉬워 보이지만 곳곳에 함정이 있어 자칫하면 끔찍한 결과를 초래한다.

문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것이다.

그냥 두면 그 클래스의 인스턴스는 오직 자기 자신과만 같게 된다.

다음에서 열거한 상황 중 하나에 해당한다면 재정의하지 않는 것이 최선이다

  • 각 인스턴스가 본질적으로 고유하다.
  • 인스턴스의 논리적 동치성을 검사할 일이 없다.
    • 논리적 동치성 : 객체 말고 값이 같은지 비교하는
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
  • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.

그럼 equals를 재정의해야 할 때는 언제일까?? 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 때다.

주로 값 클래스가 여기 해당한다.

11. equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다. 그렇지 않으면 hashCode일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet같은 컬렉션의 원소로 사용할 때 문제를 일으킬 것이다.

hashCode 재정의를 잘못했을 때 크게 문제가 되는 조항은 “논리적으로 같은 객체는 같은 해쉬코드를 반환해야 한다” 이다.

12. toString을 항상 재정의하라

Object의 기본 toString 메서드가 우리가 작성할 클래스에 적합한 문자열을 반환하는 경우는 거의 없다.

equals와 hashCode만큼 대단히 중요하진 않지만, toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅하기 쉽다.

13. clone 재정의는 주의해서 진행하라

Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스지만, 아쉽게도 의도한 목적을 제대로 이루지 못했다.

가장 큰 문제는 clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고, 그 마저도 protected라 데 있다.

메서드 하나 없는 Cloneable 인터페이스는 무슨 일을 할까?

이 인터페이스는 놀랍게도 Object의 protected 메서드인 clone의 동작 방식을 결정한다.

Cloneable을 구현한 클래스의 인스턴스에서 clone을 호출하면 그 객체의 필드들을 하나하나 복사한 객체를 반환하며, 그렇지 않은 클래스의 인스턴스에서 호출하면 CloneNotSupportedException을 던진다.

실무에서 Cloneable을 구현한 클래스는 clone 메서드를 public으로 제공하며, 사용자는 당연히 복제가 제대로 이뤄지리라 기대한다.

이 기대를 만족시키려면 그 클래스와 모든 상위 클래스는 복잡하고, 강제할 수 없고, 허술하게 기술된 프로토콜을 지켜야만 하는데, 그 결과로 깨지기 쉽고, 위험하고, 모순적인 매커니즘이 탄생한다.

생성자를 호출하지 않고도 객체를 생성할 수 있게 되는 것이다.

새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안되며, 새로운 클래스도 이를 구현해서는 안된다.

final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토 후 드물게 허용하자

기본 원칙은 ‘복제 기능은 생성자와 팩터리를 이용하는 게 최고’ 라는 것이다.

단 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라고 할 수 있다.

14. Comparable을 구현할지 고려하라

Comparable 인터페이스는 compareTo 메서드 하나만을 가지고 있는데, 이는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭 한 비교 메서드이다.

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다.

compareTo 메서드에서 필드의 값을 비교할 때 <> 연산자는 쓰지 않도록 한다.