ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective Java - 3장. 모든 객체의 공통 메서드(1/2)
    Computer - Java 2022. 12. 24. 20:25

    Object에서 final이 아닌 메서드(equals, hashCode, toString, clone, finalize) 모두 재정의(override)를 염두에 두고 설계된 것

     

    이들을 잘못 재정의하면 HashMap 등 오동작 가능

     

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

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

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

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

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

     

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

    • Object.equals의 경우 해시코드를 비교한다 - 자기자신 아니면 무조건 false

    일단 다음과 같은 경우라면 굳이 쓸 필요 없다

    • 각 인스턴스가 본질적으로 고유하다(ex. 스레드)
    • 인스턴스의 ‘논리적 동치성(logical equality)를 검사할 일이 없다(굳이 동치성 안쓴다)
    • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다
    • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다

    그러면 언제 쓰는가?

    • 값 클래스(Integer, String 등)

    물론 이미 같은 값이 둘 이상 생성되지 않음이 보장되어 있는 경우는 굳이 안 해도 됨(enum 등)

     

    Equal가 반드시 만족해야 하는 규약들(아래의 x, y, z는 모두 null이 아닌 참조 값)

    • 반사성(reflexivity) -  x.equals(x)는 모두 true
    • 대칭성(symmetry) - x.equals(y)를 만족하면 y.equals(x)도 만족
    • 추이성(transitivity) - x.equals(y), y.equals(z)가 만족하면 x.equals(z)도 만족
    • 일관성(consistency) - x.equals(y)를 여러 번 해도 언제나 같은 값
    • null-아님 : x.equals(null)은 항상 false

     

    1. 대칭성 위배 예시 

    대소문자 구분없는 CaseInsensitiveString과 구분하는 String의 equals

    까딱 잘못 구현하면 대소문자 구분없는 쪽에서는 true라고 하지만 String에서는 false로 될 수 있다

     

    2. 추이성 위배 예시 

    Point(x,y)와 그의 자식 클래스 ColorPoint(x,y,color)에서의 equals

    이 현상은 모든 객체 지향 언어의 동치관계에서 나타나는 근본적인 문제이다.

    구체 클래스를 확장해 새로운 값을 추가하면서 equals 규악을 만족시킬 방법은 존재하지 않는다.

    객체 지향적 추상화의 이점을 포기하지 않는 한은 말이다.

     

    그렇다고 해서 이렇게 한다? 리스코프 치환 법칙 위반이다.

    @Override public boolean equals(Object o){

          if (o==null || o.getClass()!=getClass()){

                return false;

          }

          return p.x==x && p.y==y;

    }

    리스코프 치환 법칙 - “부모 객체와 이를 상속한 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있어야 한다”

     

    괜찮은 우회 방식 - 상속 대신 컴포지션을 사용하라

    ColorPoint - Point를 상속하지 말고 그에 해당하는 객체를 가지면서, asPoint()를 구현하자

     

    심지어 자바 라이브러리에도 이러한 경우가 있다 - java.sql.Timestamp는 java.util.Date를 확장 후 nanoseconds 필드를 추가

    그 결과 Timestamp의 equals는 대칭서을 위배하며, Date개체와 한 컬렉션에 넣거나 서로 섞어 사용하면 엉뚱하게 동작할 수 있다.

    Timstamp를 이렇게 설계한 것은 실수이니 절대로 따라해서는 안 된다.

     

    3. 일관성 위배 예시

    equals의 판단에 신뢰할 수 없는 자원이 끼어들게 해서는 안 된다.

    ex. URL의 equals() ==> 매핑된 호스트의 IP주소를 이용해 비교(한 호스트라도 여러 IP를 가질 수 있으니 X)

     

    4. null-아님

    NullPointerException을 던지는 것을 막기 위해서 반드시 null인지 확인하는 절차를 거쳐야 한다.

    직접적으로 o==null이라고 하는 것 보다는, instanceof를 통해 묵시적으로 하는 게 깨끗함

     

     

    올바르게 equals를 작성하는 방법

    (0) 매개변수는 반드시 Object로 // 자기자신 객체 형식을 매개변수로 하면 Object의 메서드를 override가 아니라 overload한 것.

    (1) == 을 통해 자기 자신과 비교하는지 먼저 비교 - 자기 자신과 비교가 많은 경우 성능상 우위

    (2) instances 연산자로 입력이 올바른지 확인 (아니면 false, 맞으면 형변환)

    (3) 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 검사

    • float, double의 경우 Float.compare(a,b) Double.compare(a,b) 로

    (4) 대칭성, 추이성, 일관성에 대해 자문하기 - 테스트 프레임워크 : AutoValue Framework(annotation 작성)

     

     

     

     

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

     

    HashMap, HashSet과 같은 컬렉션 - hashCode를 쓴다.

     

    <조건>

    • equals 비교에 사용되는 정보가 변경되지 않았다면, 한 인스턴스의 hashCode는 프로그램 상에서 항상 같은 값
    • equals가 두 객체를 같다고 판단했으면, hashCode 또한 같아야 한다
    • equals가 두 객체를 다르게 판단하더라도 hashCode는 다를 필요는 없지만, 성능을 위해서 다른 게 좋다

    hashCode를 재정의하지 않은 경우, 다음의 결과가 의도와는 다르게 나온다.

    Map<MyObject, String> m = new HashMap<>();

    m.put(new MyObject(1), “haha”);

    m.get(new MyObject(1)); // null - hashCode가 다르다.

     

    <해시코드 생성 시>

    이상적인 해시 함수 - 주어진 인스턴스들을 32비트 정수 범위에 균일하게 분배해야 한다

    책에서 소개하는 해쉬코드 생성방법

    1. result 변수를 하나 만들어 0으로 초기화
    2. 각각의 핵심 필드에 대한 객체의 해쉬코드를 계산(핵심 필드 : equals에 쓰인 객체)
      변수 - Integer.hashCode(~~)와 같이
      참조 타입 필드이면서 equals 구현 시 재귀적으로 equals 호출 => 마찬가지로 재귀적으로 hashCode 호출
      배열 - Arrays.hashCode 혹은 0
    3.  result = 31*result + {(2)에서 계산한 값} 으로 갱신, 2 반복
    4. 2~3의 반복이 끝났다면 result 반환

     

    <유의할 점>

    • Object.hash 메서드 - 때려박을 수는 있지만 느리니깐 사용에 유의
    • lazy initialization(hashCode 호출 할때 계산) - 동기화(스레드 안정성)에 유의
    • 핵심 필드 생략은 절대 하지 말자(hash값이 몰릴 가능성 - 성능 저하 가능성)
    • hashCode 자체는 보안으로 남겨놓자(악용 가능성 + 결함 시 공표하지 않고 수정할 수 있음)

     

Designed by Tistory.