[이펙티브 자바] Object 메소드 관련 규약 + Comparable

equals 재정의


equals를 재정의할 때는 논리적 동치성을 비교할 때이며 Object 명세에 적힌 규약을 따라줘야한다.

  • 반사성
    null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true다.
    객체는 자기 자신과 같아야한다.

  • 대칭성
    null이 아닌 모든 참조 값 x,y에 대해 x.equals(y)가 true면 y.equals(x)도 true이다.
    두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.
  • 추이성
    null이 아닌 모든 참조 값 x,y,z에 대해, x.equals(y)가 true이고 y.equals(z)도 true면 x.equals(z)도 true다.
    삼단논법을 생각하면 이해하기 쉽다.

  • 일관성
    null이 아닌 모든 참조 값 x,y에 대해, x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
    두 객체가 같다면 일관성 있게 쭉 같아야 하고 다르다면 쭉 달라야한다는 뜻이다.

  • null 아니여야 한다.
    null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false다.
    모든 객체가 null과 같지 않아야 한다.

 

 

equals 메서드 구현하는 단계

  1. ==연산자를 이용해 입력이 자기 자신의 참조인지 확인한다.
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
  3. 입력을 올바른 타입으로 형변환한다.
  4. 입력 객체와 자기 자신의 대응되는 필드들이 모두 일치하는지 하나씩 검사한다.

 

 

equals를 재정의할 때 hashCode도 재정의하자

hashCode를 재정의 하지 않으면 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제가 발생할 수 있다.

equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 HashCode는 같은 값을 반환해야 한다.

 

 

 

 

 

 

toString을 항상 재정의하자


처음 toString을 사용할때는 우리가 입력한 값이 출력될 것이라고 예상하지만, 실제로 사용해보면 

[클래스이름@해시코드]가 출력되는 것을 확인할 수 있다.

 

때문에 우리는 toString을 재정의하여 우리가 바라는 적합한 문자열이 출력되게 하는 것이 바람직하다. 재정의한 toString은 프로그램은 쉽게 디버깅하게 해주며, 해당 객체에 관한 명확한 정보를 읽기 좋은 형태로 반환할 수 있게 해준다.

 

toString을 어떤 식으로 재정의할까?

  • toString은 그 객체가 가진 주요 정보 모두를 반환하도록 재정의하자.
  • toString을 재정의할 때 의도가 드러나도록 밝혀야 한다. 
    toString 재정의한 곳 위에 주석 처리를 하여 설명하는 것으로 충분하다.

 

 

 

 

 

 

Comparable 구현


Comparable 인터페이스는 메서드인 compareTo를 구현만 하면 된다.

Object의 메서드는 아니지만 성격이 비슷하다.

 

 

compareTo 메서드 규약

  • 이 객체가 주어진 객체보다 작으면 음의 정수 반환
  • 이 객체가 주어진 객체와 같으면 0 반환
  • 이 객체가 주어진 객체보다 크면 양의 정수 반환
  • 이 객체와 비교할 수 없는 타입이면 ClassCastException 발생
  • Object의 equals 메서드와 마찬가지로 반사성, 대칭성, 추이성을 만족해야 한다.

 

 

compareTo 메서드에서 필드 비교시 정적 메서드 사용

compareTo 메서드 구현시 관계 연산자인 <, >를 사용하는 대신 Integer.compare, Double.compare 같은 정적 메서드를 사용하는 것을 권한다.

 

public int compareTo(Obecjt o) {
	int result = Integer.compare(name, o.name);
    if (result == 0) {
    	result = Integer.compare(age, o.age);
        if (result == 0) {
        	result = Integer.compare(id, o.id);
        }
    }
    return result;
}

 

 

값의 차를 이용한 compare는 지양하자

static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Object o1, Object o2) {
    	return o1.hashCode() - o2.hashCode();
    }
};

이 방식은 정수 오버플로를 일으키는 등의 오류를 낼 수 있다. 때문에 이는 위 예시에서 다음 두 방법으로 대체하는 것이 좋다.

 

/* *
 * 정적 compare 메서드를 이용한 비교
 */
static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Object o1 ,Object o2) {
    	return Integer.compare(o1.hashCode(), o2.hashCode());
    }
};

/* *
 * 비교자 생성 메서드를 이용한 비교
 */
static Comparator<Object> hashCodeOrder = 
	Comparator.comparingInt(o -> o.hashCode());
};