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


내용
-  Object 객체를 만들 있는 구체 클래스지만 기본적으로 상속해서 사용 하도록 설계되어있다. Object에서 final 아닌 메서드(equals, hashCode, toString, clone, finalize) 모두 Overriding 염두에 두고 설계된 것이라 재정의 지켜야하는 일반 규약이 명확히 정의되어 있다.
- 만약 메서드를 잘못 구현하면 대상 클래스가 규약을 준수한다고 가정하는 클래스인 HashMap HashSet ) 오작동하게 만들 있다.
아이템 10. equals 일반 규약을 지켜 재정의하라.
  equals 재정의하기 쉬워보이지만 곳곳에 위험이 있다. 그냥 두면 클래스의 인스턴스는 오직 자기 자신과만 같게 되기 때문에 회피를 있다. 다음과 같은 상황 하나에 해당한다면 재정의하지 않는 것이 최선이다.
    1. 인스턴스가 본질적으로 고유하다. 값을 표현하는 것이 아니라 동작하는 개체를 표현하는 클래스가 여기 해당한다. (Thread equals)
    2. 상위 클래스에서 재정의한 equals 하위 클래스에도 들어맞는다. (Set 구현체는 AbstractSet, List 구현체 AbstractList, Map 구현체는 AbstractMap으로부터 그대로 상속 받는다.
    3. 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다. 이런 경우엔 철저히 호출되는 것을 막고 싶으면 다음과 같이 하는 것이 좋다.
@override public boolean equals(Object o) {
throw new AssertionError();
}
    4. 인스턴스의 논리적 동치성(locgical equality) 검사할 일이 없다. java.util.regex.Pattern equals 재정의해서 Pattern 인스턴스가 같은 정규표현식을 나태내는지를 검사하는 , 논리적 동치성을 검사하는 방법도 있다. 하지만 방식을 원하지 않고나 필요하지 않는다면 Object 기본 equals 해결된다.
  재정의(Override) 해야하는 경우는 객체의 식별성이 아니라 논리적 동치성을 확인해야 하는데 상위 클래스의 equals 논리적 동치성을 비교하도록 재정의되지 않았을 때다.
    - 주로 클래스(Integer String 등등)들이 여기에 해당한다. 클래스의 경우 객체가 같은지가 알고 싶은 것이 아니라 값이 같은지를 알고 싶어한다. equals 논리적 동치성을 확인하도록 재정의를 하면, 인스턴스는 값을 비교하고 Map 키와 Set 원소로 사용할 있게 된다.
    - 인스턴스가 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스라면 equals 재정의하지 않아도 된다. Enum 여기에 해당한다.
  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 이다.
  Object 명세에서 말하는 동치 관계란, 부분집합을 동치류(equaivalence class) 한다. equals 메서드가 쓸모 있으려면 모든 원소가 같은 통치류에 속한 어떤 원소와도 서로 교환할 있어야 한다.
  추이성과 대칭성은 상위 클래스에는 없는 새로운 필드를 하위 클래스에 추가하는 경우에 발생할 있다. 추가된 필드로 인해 다른 값이 Return 될수 있기 때문이다. 예를 들어 비교 대상이 다른 ColorPoint이고 위치와 색상이 같을 때만 true 반환하는 equals 일때, 일반 Point ColorPoint 비교한 결과와 둘을 바꿔 비교한 결과가 다를 있다. (Point equals 색상을 무시하고, ColorPoint equals 입력 매개변수의 클래스 종류가 다르다며 매번 false 반환 것이다.
  다음과 같은 문제는 모든 지향 언어의 동치관계에서 나타나는 근본적인 문제다. 구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 방법은 존재하지 않는다. (객체 지향적 추상화의 이름을 포기하면 해결할 있다)
  getClass() 메서드로 equals 구현하면 안된다. 리스코프 치환 원칙에 벗어나게 된다. 컬렉션 구현제에서 주어진 원소를 담고 있는지를 확인하는 방법때문에 getClass() 사용하면 안된다. instanceof() 제대로 구현했을 하위 클래스의 인스턴스를 건내줘도 정상작동을 것이다.
  구체 클래스의 하위 클래스에서 값을 추가할 방법은 없이지만, 상속 대신 컴포지션을 사용하면 있다.
  가변 객체는 비교 시점에 따라 서로 다를 수도 있는 반면, 불변 객체는 한번 같다고 객체는 끝까지 같아야하며 한번 다르면 끝까지 달라야한다. 클래스가 불변이던 가변이던 equals 판단에 신뢰할 없는 자원이 끼어들게 해서는 안된다. 또한 항시 메모리에 존재하는 객체만을 사용한 결정적(deterministic) 계산만 수행해야한다.
  null 아님은 검사하기 위해서 종종 o.equals(null) 사용하는 경우가 있다. 종종 NullPointerException 발생하기도 한다. 일반 규약은 이런 경우도 허용하지 않는다. 많은 클래스가 다음 코드처럼 입력이 null인지를 확인해 자신을 보호한다. 만약 동치성을 검사하려면 equals 건네받은 객체를 적절히 형변환한 필수 필드들의 값을 알아내야한다. 그러려면 형변환에 앞서 instanceof 연산자로 입력 매개변수가 올바른 타입인지 검사해야한다. equals 타입을 확인하지 않으면 잘못된 타입이 인수로 주어졌을 ClassCastException 던져서 일반 규약을 위배하게 된다.
  equals 메서드를 구현하는 방법
     1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
     2. instanceof 연산자로 입력이 올바른 타입인지 확인한다. 가끔은 클래스가 특정 인터페이스가 되었을 , 인터페이스는 자신을 구현한 서로 다른 클래스끼리도 비교할 있도록 equals 규약을 수정하기 때문이다.
     3. 입력을 올바른 타입으로 형변환한다.
     4. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다. 모든 필드가 일치하면 true, 아니면 false 반환한다.
  비교할 , float double 제외한 기본 타입 필드는 == 연산자를 사용하고, 참조 타입 필드는 각각의 equals 메서드로, float double Float.compare, Double.compare 비교한다. (Float.NaN, -0.0f 때문) equals 비교할 있지만 오토박싱을 수반할 있으니 성능상 좋지 않다.
  복잡한 필드를 갖는 클래스(대표적으로,  CaseInsensitiveString) 경우, 필드의 표준형(canonical form) 저장해둔 표준형끼리 비교하면 훨씬 경제적이다.
  필드의 비교순서가 성능을 좌우한다. 다를 가능성이 크거나 비교하는 비용한 필드는 먼저 비교하는 것이 좋다.
  동기화용 (lock) 필드 같이 객체의 논리적 상태와 관련 없는 필드는 비교를하면 안된다. 핵심 필드로부터 계산해낼 있는 파생 필드를 비교하는 쪽이 빠를때도 있다.
  equals 재정의할 , hashCode 반드시 재정의하자.
  너무 복잡하게 해결하려고 하지말자. 필드들의 동치성만 검사해도 equals 규약을 어렵지 않게 지킬수 있다. 별칭(alias) 심볼릭 링크등은 비교하지 않는 것이 좋다.
  object외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말자. 이렇게 선언할 경우, Object.equals 재정의가 아닌 다중정의를 것이다.
  구글의 AutoValue 프레임워크를 활용하자
아이템 11. equals 재정의 하려거든 hashCode 재정의하라
  equals 재정의한 클래스 모두에서 hashCode 재정의를 해야 한다. 그렇지 않으면 hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 문제를 일으킬 것이다.
  equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 객체의 hashCode 메서드는 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다. (, 애플리케이션을 다시 실행한다면 값이 달라져도 상관없다.)
  equals(Object) 객체를 같다고 판단했다면, 객체의 hashCode 똑같은 값을 반환해야 한다.
  equals(Object) 객체를 다르다고 판단했더라도, 객체의 hashCode 서로 다른 값을 반환할 필요는 없다. , 다른 객체에 대해서는 다른 값을 반환해야 해시테블의 성능이 좋아진다.
  hashCode 재정의를 잘못했을 크게 문제가 되는 조항은 번째이다. 논리적으로 같은 객체는 같은 해시코드를 반환해야한다. 아이템 10에서 설명했듯, equals 물리적으로 다른 객체를 논리적을 같다고 있다. 하지만 Object 기본 hashCode 메서드는 둘이 전혀 다르다고 판단하여, 규약과 달리 서로 다른 값을 반환한다.
  이상적인 해시 함수는 주어진 서로 다른 인스턴스들을 32 정부 범위에 균일하게 분배해야 한다. 다음은 좋은 hashCode 작성하는 간단한 요령이다.
    1. int 변수 result 선언한 값을 c 초기화한다. 이때 c 해당 객체의 번째 핵심 필드를 2.a 단계 방식으로 계산한 해시코드다. (핵심 필드란 equals 비교에 사용되는 필드)
    2. 해당 객체의 나머지 핵심 필드 f 각각에 대해 다음 작업을 수행한다.
      a. 해당 필드의 해시 코드 c 계산한다.
        1. 기본 타입 필드라면, Type.hashCode(f) 수행한다. (Type 해당 기본 타입의 박싱 클래스)
        2. 참조 타입 필드면서 클래스의 equals 메서드가 필드의 equals 재귀적으로 호출해 비교한다면, 필드의 hashCode 재귀적으로 호출한다. 계산이 복잡해질 같으면, 필드의 표준형을 만들어 표준형의 hashCode 호출한다. 필드의 값이 null이면 0 사용한다.
        3. 필드가 배열이라면, 핵심원소 각각을 별도 필드처럼 다룬다. 이상의 규칙을 재귀적으로 적용해 핵심 원소의 해시코드를 계산한 다음, 단계 2.b 방식으로 갱신한다. 배열에 핵심 원소가 하나도 없다면 단순히 상수(0 추천) 사용한다.  모든 원소가 핵심 원소라면 Arrays.hashCode 사용한다.
      b. 단계 2.a 에서 계산한 해시코드 c result 갱신한다. 코드로는 다음과 같다. result = 31 * result + c;
    3. result 반환한다.
  다른 필드로부터 계싼해 낼수 있는 필드는 모두 무시해도 된다. 또한 equals 비교에 사용되지 않는 필드는 무조건 제외한다.
  2.b 예제 코드에서 곱할 값을 31 정한 이유는, 홀수이고 소수이기 때문이다. 짝수에 오버플로가 발생하면 데이터를 잃게 된다. 그리고 2 곱할 경우 시프트연산하는 효과를 나타낸다.
  해시 충돌이 더욱 적은 방법을 써야한다면 구아바의 com.google.common.hash.Hashing 참고하자.
  해시코드 계산시 성능을 위해 핵심 필드를 생략해서는 안된다. 나중에 성능이 심각하게 나빠질 있다.
  hashCode 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 말자. 그래야 클라이언트가 값에 의지하지 않게 되고, 추후에 계산 방식을 바꿀 수도 있다.
  AutoValue 프레임워크를 사용하면 자동으로 생성이된다.
아이템 12. toString 항상 재정의하라
  toString 일반 규약에 따르면 '간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환해야 한다. , 모든 하위 클래스에서 메서드를 재정의하라고 되어 있다.
  toString 객체가 가진 주요 정보 모두를 반환하는 좋다.
  toString 구현할 때면 반환값의 포맷을 문서화할지 정해야한다. 문서화할 , 포맷을 명시하면 객체는 표준적이고, 명확하고, 사람이 읽수 있게 되야한다.
  포맷을 명시하기로 했으면, 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 있는 정적 팩터리나 생성자를 함께 제공해주면 좋다. (BigInteger, BigDecimal 대부분의 기본 타입 클래스가 여기 해당함)
  포맷을 명시하면, 바꾸기가 어렵다. 이유는 이를 사용하는 프로그래머들이 포맷에 맞춰 개발을 하기 때문이다. 반대로 포맷을 명시하지 않는다면 유연성을 얻을 있다.
  포맷을 명시하든 아니든 만든 의도를 명확히 밝혀야 한다.
    toString 반환 값에 포함된 정보를 얻어올 있는 API 제공하자. 이유는 필요한 값을 얻기 위해선 toString 반환값을 파싱할 밖에 없기때문이다. 그럼 성능이 나빠지고 필요하지 않는 작업도 하게 된다.
  정적 유틸리티 클래스, 열거 타입는 toString 재정의할 필요가 없다. 하지만 하위 클래스들이 공유해야할 문자열 표현이 있는 추상 클래스라면 toString 재정의해줘야 한다.
아이템 13. clone 재정의는 주의해서 진행하라
  cloneable 복제해도 되는 클래스임을 명시하는 용도의 Mixin Interface지만, clone 메서드가 선언된 곳이 Cloneable 아닌 Object이고, 그마저도 protected라는데 문제가 있다. 그래서 Cloneable 구현하는 것만으로는 외부 객체에서 clone 메서드를 호출할 없다.
  Cloneable 구현한 클래스의 인스턴스에서 clone 호출하면 객체의 필드들을 하나하나 복사한 객체를 반환하며, 그렇지 않은 클래스의 인스턴스에서 호출하면 "CloneNotSupportedException" 던진다. 이는 상당히 이례적으로 사용한 예니 따라하지 말것.
  clone 메서드의 일반 규약은 허술하다. 강제성이 없다는 점만 빼면 생성자 연쇄(constructor chaining) 비슷하다. , clone 메서드가 super.clone 아닌, 생성자를 호출해 얻은 인스턴스를 반환해도 컴파ㄹ러는 불평하지 않을 것이다. 하지만 클래스의 하위 클래스에서 super.clone 호출한다면 잘못된 클래스의 객체가 만들어져서, 하위 클래스의 clone 메서드가 제대로 동작하지 않는다.
  제대로 동작하는 clone 메서드를 구현하는 방법
    1. super.clone 호출한다. 그렇게 얻은 객체는 원본의 복제본일 것이다. 클래스에 정의된 모든 필드는 원본 필드와 똑같은 값을 갖는다.
    2. 재정의된 clone 메서드가 동작하게 하려면 해당 클래스 선언에 Cloneable 구현한다고 추가해야한다. Object clone 메서드는 Object 반환하지만 해당 클래스의 clone 해당 클래스를 반환하게 했다. 자바가 공변 반환 타이핑(covariant return typing) 지원하니 가능하고 이를 권장한다. 따라서 재정의한 메서드의 반환 타입은 상위 클래스의 메서드가 반환하는 타입의 하위 타입일 있다.
  clone 메서드는 사실상 생성자와 같은 효과를 낸다. , clone 원본 체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야한다.  배열인 경우 그냥 clone 메서드를 사용할 경우 참조를 하기 때문에 원본이 수정이 되면 복제본도 수정된다. 가장 쉬운 해결 방법으로는 배열의 clone 재귀적으로 호출해 주는 것이다.(p81 예제 참조)
  배열의 clone 재귀적으로 호출해도 해시테이블용 clone 메서드는 오작동할 있다. 복제본은 자신만의 버킷 배열을 갖지만, 배열은 원본과 같은 연결 리스트를 참조하여 원복과 복제본 모두 예기치 않게 동작할 가능성이 생기기 때문이다. 이를 해결하기 위해선 버킷을 구성하는 연결 리스트를 복사해야한다.(p82참조)
  버킷을 구성하는 연결리스트를 복사하려면 적절한 크기의 새로운 버킷 배열을 할당한 다음 원래의 버킷 배열을 순회하며 비지 않는 버킷에 대해 깊은 복사를 수행한다. 이때 자신이 가ㅣ키는 연결 리스트전체를 복사하기 위해 자신을 재귀적으로 호출한다. (오버플로를 조심하자)
  상속해서 쓰기 위한 클래스에서는 Cloneable 구현해서는 안된다.
  clone 메서드는 적절히 동기화해줘야 한다. , Cloneable 구현하는 모든 클래스는 clone 재정의해야한다. 이때 접근자는 public, 반환타입은 클래스 자신으로 변경한다. super.clone 호출한 필요한 필드를 전부 적절히 수정한다. 객체의 내부 '깊은 구조' 숨어 있는 모든 가변 객체를 복사하고, 복사본이 가진 객체 참조 모두가 복사된 객체를 가리키게 한다.
  복사 생성자와 복사 팩터리라는 나은 객체 복사 방식을 제공할 있다. 해당 클래스가 구현한 인터페이스 타입의 인스턴스를 인수로 받을 있다.
아이템 14. Comparable 구현할지 고려하라
  CompareTo equals 다른 점은 CompareTo 단순 동치성 비교에 더해 순서까지 비교할 있으며 제네릭하다. Comparable 구현했다는 것은 클래스의 인스턴스들에 순서가 있음을 뜻한다. 그래서 Comparable 구현한 객체들의 배열은 다음처럼 손쉽게 정렬 있다.
  CompareTo 메서드는 객체와 주어진 객체의 순서를 비교한다. 객체가 주어진 객체보다 작으면 음의 정수를 같으면 0 크면 양의 정수를 반환한다. 객체와 비교를 없다면 ClassCastException 던진다.
  CompareTo 메서드의 일반 규약은 equals 규약과 비슷하다.
    1. Comparable 구현한 클래스는 모든 x,y 대해 sgn(x.compareTo(y) == -sgn(y.compareTo(x))여야 한다.
    2. Comparable 구현한 클래스는 추이성을 보장해야한다. , (x.compareTo(y) > 0 && y.compare(z) > 0)이면 x.compareTo(z) > 0 이다.
    3. Comparable 구현한 클래스는  모든 z 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) == sgn(y.compareTo(z)).
    4. 이번 권고가 필수는 아니지만 지키는 좋다. (x.compareTo(y) == 0) == (x.equals(y))여야 한다. Comparable 구현하고 권고를 지키지 않는 모든 클래스는 사실을 명시해야한다. (, "주의 : 클래스의 순서는 equals 메서드와 일관되지 않다")
  위의 규약(1,2,3) compareTo 메서드로 수행하는 동치성 검사도 equals 규약과 똑같이 반사성, 대칭성, 추이성을 충족해야 함을 뜻한다. 그래서 주의사항도 똑같다. 기존 클래스를 확장한 구체 클래스에서 새로운 컴포넌트를 추가했다면 compareTo 규약을 지킬 없다. 만약 Comparable 구현한 클래스를 확장해 컴포넌트를 추가하고 싶다면, 확장하는 대신 독립된 클래스를 만들고, 클래스에 원래 클래스의 인스턴스를 가리키는 필드를 두고, 내부 인스턴스를 반환하는 '' 메서드를 제공하면 된다.
  4 규약을 지키지 않아도 작동은 하지만, 컬렉션이 구현한 인터페이스에 정의된 동작과 엇박자를 것이다. 이유는 정렬된 컬렉션들은 동치성을 비교할 , equals 대신 compareTo 사용하기 때문이다.
  compareTo 메서드 작성 요령은 equals 비슷하다. 가지 차이점만 주의하면 된다. Comparable 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수 타입은 컴파일타임에 정해진다. 입력 인수의 타입을 확인하거나 형변환할 필요가 없다는 뜻이다.
  compareTo 메서드는 팔드가 동치인지를 비교하는 것이 아니라 순서를 비교한다. 객체 참조 필드를 비교해야 한다면 비교자(Comparator) 대신 사용한다.
  compareTo 메서드에서 관계 연산자 < > 사용하는 이전 방식(자바 6버전 이하) 거추장스럽고 오류를 유발하니, 이제는 추천하지 않는다.
  자바 8에서는 Comparator 인터페이스가 일련의 비교자 생성 메서드와 합쳐져 메서드 연쇄 방식으로 비교자를 생성할 있게 되었다. (p92 예제 참조) 예제를 보면 첫번째로 쓰인 comparingInt 객체 참조를 int 타입 키에 매핑하는 추출 함수(key extractor function) 인수로 받아, 키를 기준으로 순서를 정하는 비교자를 반환하는 정적 메서드이다. 두번째 비교 생성 메서드인 thenComparingInt 수행한다.  thenComparingInt Compartor 인스턴스 메서드로 int 추출자 함수를 입력 받아 다시 비교자를 반환한다. thenComparingInt 원하는 만큼 호출할 있다. thenComparingInt 번째 비교자를 적용한 다음 새로 추출한 키로 추가 비교를 수행한다. (올바른 예제 p94 참조)


요점
- 필요한 경우가 아니면 equals 재정의하지 말자. 대부분 Object equals 원하는 비교를 정확히 수행한다. 재정의를 해야할땐, 규약(반사성, 추이성, 대칭성,일관성, null 아님) 지켜줘야한다.
- equals 재정의 할때 hashCode 반드시 재정의해야 한다. 그렇지 않으면 재대로 동작하지 않을 것이다. 재정의한 hashCode Object API문서에 기술된 일반 규약을 따라야 하며, 서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야한다.
- 모든 구체 클래스에서 Object toString 재정의하자. 상위 클래스에서 이미 알맞게 재정의한 경우는 예외다. toString 재정의한 클래스는 클래스를 위한 디버깅하기 쉽게해준다. toString 객체에 대한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야한다.
- Cloneable 몰고 모든 문제를 되짚어봤을 , 새로운 인터페이스를 만들 때는 절때 Cloneable 확장해서는 되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스는 위험 부담이 적지만, 성능 최적화 관점에서 검토한 별다른 문제 없을때 드물게 허용해야한다. 기본 원칙은 생성자와 팩터리를 이용하는 것이다. , 배열만은 clone 메서드의 예외이다.
- 순서를 고려해야 하는 클래스를 작성한다면 Comparable 인터페이스를 구현하여, 인스턴스를 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어어러지도록 해야한다. compareTo 메서드에서 필드의 값을 비교할 < > 연산자는 쓰지 말아야한다. 대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.


노트 요약
- 리스코프 치환 원칙 : 어떤 타입에 있어 중요한 속성이라면 하위 타입에서도 마찬가지로 중요하다. 따라서 타입의 모든 메서드가 하위 타입에서도 똑같이 작동해야한다.
- 생성자 연쇄(constructor chaining) : class 생성했을 , 생성자를 따로 설정하지 않아도 기본적으로 티폴드 생성자가 super()l 가지고 만들어지고 super() 부모 class 생성자를 먼저 실행하게 된다.


3장 모든 객체의 공통 메서드는 일반 규약을 지켜서 Object의 메서드를 오버라이딩을 해야한다. 우리가 자주 쓰는 다른 객체(Map, Set등)에 영향을 줄 수 있기 때문이다. Google AutoValue와 같은 프레임워크를 사용해서 실수를 줄이는 것이 좋다.


댓글