본문 바로가기

STUDY REVIEW/이펙티브 자바 독서스터디

[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 3장 모든 객체의 공통 메서드

3장 모든 객체의 공통 메서드
아이템 10. equals는 일반 규약을 지켜 재정의하라
아이템 11. equals를 재정의하려거든 hashCode도 재정의하라
아이템 12. toString을 항상 재정의하라
아이템 13. clone 재정의는 주의해서 진행하라
아이템 14. Comparable을 구현할지 고려하라

 

읽고 느낀 점

 

나한테 equals은 if문에만 쓰는 용도였는데, 이걸 재정의하라니 다른 사람들은 어떻게 사용하는지 궁금해져 블로그들을 찾아보았다. 다들 이펙티브 자바 독서에만 사용하는 것 같다 ... 실력을 키워 검색능력을 키워야하는 것인가 아니면 코드를 창조해야하는 것인가 의문이 생겼다. 

 

toString은 예전 순수자바를 사용하는 회사에서도 재정의를 하여 쓰고 있어 이해가 잘되었다. 어찌되었든 equals를 재정의하려거든 hashCode도 재정의하라는 교훈을 얻었다. Comparable을 현재 회사에서 찾아보니 많은 코드들이 나온다. 코드를 공부하여 나중에 추가로 정의하기로 혼자 마음을 먹었다. 

 

이펙티브 자바는 한번 스터디를 끝낸 후에 다시 한번 읽어봐야겠다. 


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

꼭 필요한 경유가 아니면 equals을 재정의하지 말자.
많은 경우에 Object의 equals가 원하는 바를 정확히 수행해준다.

 

equals를 재정의 하면 안 되는 경우

  1. 각 인스턴스가 본질적으로 고유할 때 값 클래스(Integer나 String처럼 값을 표현하는 클래스)가 아닌 동작하는 개체를 표현하는 클래스
    ex) Thread
  2. 인스턴스의 '논리적 통치성'을 검사할 일이 없을 때
    ex) java.util.regax.Pattern은 equals를 재정의해 두 Pattern의 정규표현식을 비교
  3. 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞을 때
    ex) Set은 AbstractSet이 구현한 equals를 상속, List는 AbstractList, Map은 AbstractMap
  4. 클래스가 private이나 package-private이고 equals를 호출할 일이 없을 때 아래와 같이 구현해 equals가 실수로라도 호출되는 걸 막을 수 있다.
@Override public boolean equals(Object o) {
	throw new AssertionError(); // 호출 금지!
}

 

equals를 재정의 해야 하는 경우

객체 식별성(object identity; 두 객체가 물리적으로 같은가)이 아닌 '논리적 동치성'을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의 되지 않았을 때 (주로 값 클래스)

 

ex) 두 값 객체를 equals로 비교하는 경우, 객체가 같은지가 아니라 값이 같은지를 알고싶을 것이다.

 

equals 메서드 재정의 일반 규약: 동치관계

  • 반사성(reflexivity)
    : null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true다.
  • 대칭성(symmetry)
    : null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)가 true면 y.equals(x)도 true다.
  • 추이성(transitivity)
    : null이 아닌 모든 참조 값 x, y, z에 대해, x.equals(y)가 true이고, y.equals(z)도 true면 x.equals(z)도 true다.
  • 일관성(consistency)
    : null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)를 반복해서 호출하면 항상 true이거나 false다.
  • **null-아님**
    : null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false다.

 


 

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

equals를 재정의할때는 반드시 hashCode도 재정의해야 한다.
제정의한 hashCode는 일반 규약을 따라야한다.

 

좋은 hashCode 작성 방법

  • 이미 정의된 hashCode(Integer.hashCode, Arrays.hashCode 등)를 최대한 사용해라
  • equals 비교에 사용되지 않은 필드는 hashCode 에서도 반드시 제외해야한다.
  • 해시 충돌이 더욱 적은 방법을 써야한다면 Guava Hashing을 참고하자
  • (주의) hashCode 반환 값의 생성 규칙을 사용자에게 알리지 말아라
  • 사용하는 개발자가 이 값에 의지하고 코드에 반영해버리면 문제가 생길 수 있다.

 



아이템 12. toString을 항상 재정의하라

모든 구체 클래스에서 Object의 toString을 재정의하자.
상위 클래스에서 재정의한 경우는 예외이다.
toString를 재정의한 클래스는 사용하기도 즐겁고 그 클래스를 사용한 시스템을 디버깅하기 쉽게 해준다.
toString는 해당 객체에 대한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야한다.

좋은 toString 재정의 방법

  • 객체가 가진 주요 정보 모두를 반환해야 한다.
  • 주석으로 toString이 반환하는 포맷을 명시하든 아니든 의도를 명확하게 해야 한다.
  • toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자. 없다면 이 정보가 필요한 개발자는 toString의 반환값을 파싱할 수 밖에 없다.

 

toString 재정의가 필요 없는 경우

  • 대부분의 정적 유틸리티 클래스
  • 대부분의 열거(enum) 타입
  • 수퍼 클래스에서 이미 적절하게 재정의한 경우

 

실무에서는 ToStringBuilder을 추천

apache-commons-lang3 라이브러리를 사용하면 간편하게 toString 포맷을 이용할 수 있습니다.

public String toString() {
    /*
     * 두 번째 인자를 변경하여 포맷을 바꿀 수 있다.
     * ToStringStyle.JSON_STYLE 등
     */
    return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}

 


 

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

cloneable이 몰고 온 모든 문제를 되짚어 봤을 때, 새로운 인터페이스를 만들 때는 절대 cloneable을 확장해선 안된다.
새로운 클래스도 이를 구현하면 안된다.
final 클래스라면 cloneable을 구현해도 위험하지 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 때만!
드물게 허용해야 한다.
기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 최고'라는 것이다.
단 배열만은 clone 메서드 방식이 가장 깔끔한 예외라고 할 수 있다.

 



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

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현한다.
그 인스턴스를 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야한다.
박싱된 기본 타입 클래스가 제공하는 정적 Compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자!

compareTo 메서드의 일반 규약

  • 이 객체와 주어진 객체의 순서 비교
    • 이 객체가 주어진 객체보다 작으면 음의 정수를 반환한다.
    • 이 객체가 주어진 객체와 같으면 0을 반환한다.
    • 이 객체가 주어진 객체보다 크면 양의 정수를 반환한다.
    • 이 객체와 비교할 수 없는 타입의 객체면 ClassCastException 예외를 던진다.
  • 대칭성: Comparable을 구현한 클래스는 모든 x, y에 대해 sng(x.compareTo(y)) == -sgn(y.compareTo(x)) 이다.
  • 추이성: Comparable을 구현한 클래스는 x.compareTo(y) > 0 && y.compareTo(z)이면, x.compareTo(z) > 0 이다.
  • 반사성: Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0이면, sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다.
  • 동치성: 필수는 아니지만 좋기면 좋다. (x.compareTo(y) == 0) == (x.equals(y)) 여야 한다.
    • Comparable을 구현했지만 이 사항을 지키지 않은 모든 클래스는 그 내용을 명시하면 좋다.

 

compareTo 메서드 작성법

equals와 비슷하지만 몇 가지 차이점이 있다.

  • 인자의 타입을 확인한다거나 형변환할 필요가 없다. 제네릭 인터페이스이기 때문이다.
  • 타입이 잘못됐다면 컴파일 시점에 오류가 발생한다. null을 입력한 경우 NullPointerException 예외를 던져야 한다.
  • 각 필드가 동치인지를 비교하는 게 아니라 그 순서를 비교한다.
  • 객체 참조 필드를 비교하려면 compareTo 메서드를 재귀적으로 호출한다.
  • Comparable을 구현하지 않은 필나 표준이 아닌 순서로 비교해야 한다면 비교자(Comparator)를 대신 사용한다.
  • 자바 7부터는 기본 정수 타입을 비교할 때 관계 연산자 <와> 을 사용하지 않고 compare를 사용하라.
  • 핵심적인 피륻부터 비교한다. 비교 결과가 바로 나온다면, 즉 순서가 바로 결정되면 거기서 결과를 곧바로 반환하자.