03 모든 객체의 공통 메서드 (ITEM 10 ~ 14)

 

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

 

다음의 경우에 하나라도 해당된다면 equals 메서드를 재정의할 필요 없다.

 

  • 각 인스턴스가 본질적으로 고유하다.
  • 인스턴스의 논리적 동치성 (logical equality)을 검사할 일이 없다.
  • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.

 

결국 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 이 아님 : null 이 아닌 모든 참조 값 x 에 대해, x.equals(null) 은 false 다.

 

 

주의 사항

 

가끔 아래와 같이 equals 메서드 재정의시 매개변수 타입을 Object 로 하지 않는 개발자가 있다.

Object 클래스의 equals 메서드는 매개변수 타입이 Object 이니 아래의 코드는 재정의가 아닌 다중정의한 꼴이 되니 주의하자.

public boolean equals(Target o) {

    ...
}

 

 

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

 

HashMap 과 HashSet 같은 자료구조에서는 아이템들이 논리적으로 같다고 판단하기 위해서 내부적으로 equals 메서드 뿐만 아니라 hashCode 메서드의 반환값도 사용해서 비교한다. 따라서 HashMap 또는 HashSet 에서 사용될 두 아이템을 논리적으로 같다고 설정하기 위해 equals 메서드는 재정의했지만, hashCode를 재정의하지 않았다면 equals 메서드는 true를 반환하더라도 hashCode 서로 다른 해싱된 값을 반환하기 때문에 HashMap 또는 HashSet 에서는 같은 아이템으로 보지 않을 것이다.

 

 

ITEM 12 toString 을 항상 재정의하라.

 

구체클래스에서 Obejct 로 부터 상속받은 toString 메서드를 호출하면 객체의 ID 값을 반환할 것이다. 객체의 ID 뿐만 아니라

객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환하기 위해 모든 구체 클래스에서 Object의 toString 메서드를 재정의하자. 디버깅이 편리해지고 개발 생산성도 증가될것이다.

 

 

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

 

  • Cloneable을 구현하는 모든 클래스는 아래와 같이 clone 메서드를 재정의해야 한다. 그래야 CloneNotSupportedException 예외가 throw 되지 않는다. 
  • 접근 제한자는 public, 반환 타입은 클래스 자신으로 아래와 같이 변경한다.

 

@Override
public Target clone() throws CloneNotSupportedException {
    return (Target) super.clone();
}

 

 

  • Object 의 clone 메서드는 복제하려는 원본 클래스가 가지는 프로퍼티들이 primitive 하다면 완벽한 복제가 이루어지지만 값 클래스가 존재하여 shallow copy 가 이루어진다면 불변식을 해칠 수 있다. 그러므로 clone을 재정의하면서 동시에 super.clone 을 호출 후 필요한 필드를 적절히 수정하여 deep copy 되게 구현해줘야한다.
  • 항상 Cloneable 을 구현하는것이 좋은건 아니니 상황에 따라 복사 생성자(자신과 같은 클래스의 인스턴스를 인수로 받아 프로퍼티 머지를 진행) 또는 복사 팩토리(정적 팩토리 메서드)를 사용하자.

 

 

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

 

순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하여 이 인터페이스를 활용하는 수 많은 제네릭 알고리즘과 컬렉션의 힘을 누리자.

 

그리고 구현시 아래의 compareTo 규약을 유념하자

 

  • equals 의 규약과 동일하게 반사성, 대칭성, 추이성을 충족해야한다.
  • 기존 클래스를 확장한 구체 클래스에서 새로운 값 컴포넌트를 추가했다면 compareTo 규약을 지킬 방법이 없으므로 상속이 아닌 컴포지션으로 만들자.
  • compareTo 동치성 결과와 equals 동치성 결과를 일치시키자. 이는 정렬되어있는 컬렉션을 오류없이 사용하기 위함이다.
  • compareTo 인수에 null 을 넣어 호출하려할 경우 NPE 를 throw 하자.
  • 클래스의 핵심 필드가 복수개라면, 가장 핵심적인 필드부터 비교하자. 비교 결과가 0이 아닐경우 더 이상 비교할 필요 없으니 종료시키면된다.
  • Comparable 을 구현하는것 대신 유용한 Comparator 를 사용해도 무방하다.
  • 이따금 값의 차를 이용해 -1 , 0, 1 을 반환하도록 하는 코드를 작성할 수 있는데 이는 정수 오버플로우를 일으키거나 부동소수점 계산 방식에 따른 오류를 낼 수도 있다. 따라서 아래와 같은 두 방식 중 하나로 사용해야한다.

 

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());

 

 

 

 

 

 

'📚 Book > Effective Java' 카테고리의 다른 글

02장 객체 생성과 파괴 (ITEM 01 ~ 09)  (0) 2021.07.30