Java/Effective Java

[Effective Java] 3장. 모든 객체의 공통 메서드

SeungbeomKim 2024. 8. 8. 23:37

 

[item-10] equals는 일반 규약을 지켜 재정의하라

 

equals 메서드는 반드시 동치관계(equivalence relation)를 구현하며, 다음을 만족합니다.

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

 

[item-11] equals를 재정의하려거든 hashCode도 재정의하라

 

hashCode는 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 쓰이는 메서드

  • 특정한 객체를 해쉬코드(일정한 길이의 문자열로 표시)

Object의 명세에는 hashCode에 관련한 규약이 정해져 있습니다

 

오류 발생 상황

Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
m.get(new PhoneNumber(707, 867, 5309)) // "Jenny"가 아닌 null이 나온다.

 

좋은 해시코드 작성요령

// One-line hashCode method - mediocre performance (성능이 민감하지 않은 경우)
@Override public int hashCode() {
	return Objects.hash(lineNum, prefix, areaCode);
}

// hashCode method with lazily initialized cached hash code
private int hashCode; // Automatically initialized to 0

@Override public int hashCode() {
	int result = hashCode;
	if (result == 0) {
		result = Short.hashCode(areaCode);
		result = 31 * result + Short.hashCode(prefix);
		result = 31 * result + Short.hashCode(lineNum);
		hashCode = result;
	}
	return result;
}

 

해시코드 생성시 주의사항

  1. 성능을 높이기 위해 해시코드를 계산할 때 핵심 필드를 생략해서는 안됩니다. 해시코드의 분포를 고르게 나누어주는 효과가 있기 때문입니다.
  2. hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 않는 것이 좋습니다. 그래야 클라이언트가 이 값에 의지하지 않고, 추후에 계산방식을 바꿀 수 있습니다.

 

[item-12] 항상 toString을 재정의하라

  • Object에서 정의한 toString()을 재정의하고 사용하지 않으면, 클래스 이름@16진수로 표시한 해시코드 포맷으로 반환
  • 디버깅용으로 많이 사용

 

[item-13] Clone 재정의는 주의해서 진행하라

  • 복제해도 되는 클래스임을 명시하는 용도의 인터페이스
  • Clonable 자체에는 메서드가 없고, Cloneable을 구현하고 Object의 clone() 메서드를 호출하면 값을 복사한 객체를, 그렇지 않으면 CloneNotSupportedException을 반환

 

메서드 재정의 방법

  1. 접근 제한자는 public으로 함
  2. 반환 타입은 클래스 자신으로 변경
  3. 가장 먼저 super.clone() 호출
  4. 필요한 필드는 모두 수정

 

Example

@Override
public PhoneNumber clone() {
    try {
        return (PhoneNumber) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();	// 일어날 수 없는 일이다.
    }
}

 

 

  • 재정의한 메서드의 반환 타입은 상위 클래스의 메서드가 반환하는 타입의 하위 타입
  • 클라이언트가 형변환하지 않아도 되게끔, super.clone()에서 얻을 객체를 반환하기 전에 형변환
  • try-catch로 감싼 이유는 Object의 clone 메서드가 검사 CloneNotSupportedException을 던지도록 선언되었기 때문입니다. → Clonable을 구현하는데 예외를 던질 필요가 X

 

[item-14] Comparable을 구현할지 고려하라

Comparable 인터페이스의 유일한 메서드이다. equals처럼 동작하지만, 추가적으로 순서까지 비교할 수 있다. (반환 값이 1이면 더 큰 값, 0이면 동일한 값, -1이면 작은 값을 의미한다) Comparable을 구현했다는 것은 그 클래스의 인스턴스들 간에 자연적인 순서가 있음을 뜻합니다

CompareTo의 규약

1. 반사성 : 두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야 .

2. 추이성 : 첫 번째보다 두 번째 객체가 크고, 두 번째 객체가 세 번째 객체보다 크면, 세 번째 객체는 첫 번째 객체보다 커야 한다.

3. 대칭성 : 크기가 같은 객체들끼리는 어떤 객체와 비교하더라도 항상 같아야 한다.