03. 모든 객체의 공통 메서드
Object 클래스에서 다루는 오버라이드를 예상하고 만든 공통 메서드를 다룰 예정
#
Item 10. equals는 일반 규약을 지켜 재정의하라#
equals를 재정의하지 말아야할 케이스- 각 인스턴스가 본질적으로 고유하다
- 인스턴스의 논리적 동치성(logical equality)를 검사할 일이 없다
- 상위 클래스에서 재정의한 equals가 하위 클래스에서도 딱 들어 맞는다
- 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다
#
equals를 재정의하는 케이스- 물리적 동치성이 아니라 논리적 동치성을 활용해야 하지만,
- 상위 클래스에서 equals의 구현이 논리적 동치성을 비교하지 않도록 구성되어 있을 때.
- 값 클래스 (Integer, String, Long 등) 이 주로 해당 케이스에 해당
#
Object 클래스의 equals 일반 규약equals는 동치 관계를 구현하며, 다음 관계를 만족
danger
아래를 지키지 못하면 비교 동작, 컬렉션과 같은 곳에서 이상하게 동작할 수 있으니 주의!
모든 객체는 null이 아님을 전제함
- 반사성(Reflexivity):
x.equals(x)
is true - 대칭성(Symmetry):
x.equals(y)
is true =>y.equals(x)
is true - 추이성(Transivity):
x.equals(y)
is true,y.equals(z)
is true =>x.equals(z)
is true - 일관성(Consistency): 항상
x.equals(y)
의 결과는 같은 결과를 보장한다 - null이 아님:
x.equals(null)
isfalse
#
규약을 잘 지킨 equals 구현법==
연산자를 사용해 입력이 자기 자신의 참조인지 확인 (early return)instanceof
연산자로 입력이 올바른 타입인지 확인- null-아님 규칙은 null 검사 필요없이 instanceof 로 구현 가능
- 입력을 올바른 타입으로 형변환
- 입력 객체와 자기 자신의 핵심 필드들이 모두 일치하는지 하나씩 검사
- 기본타입 필드는
==
- 참조타입 필드는
equals
- float와 double은
Float.compare(float, float)
,Double.compare(double, double)
- 성능을 위해 비교 연산이 가벼운 것부터 진행
- 기본타입 필드는
- 다 구현 후 일반규약 잘지켰는지 검토
- 대칭성, 추이성, 일관성 잘 지켜졌나~
#
주의사항- equals를 재정의할 땐 hashCode도 반드시 재정의 (Item 11)
- 너무 복잡하게 해결 X
- Object 타입외 인자를 가지는 equals 구현 X
#
10 핵심정리- 꼭 필요한 경우(논리적 동치 같은 케이스)가 아니라면 equals 오버라이딩 X
- AutoValue 프레임워크로 equals 구현 및 테스트를 자동으로 할 수 있음
#
Item 11. equals를 재정의하려거든 hashCode도 재정의하라#
hashCode 규약- equals 비교에 사용되는 정보가 변하지 않았다면, 앱이 실행되는 동안 객체의 hashCode 는 일관성을 지녀야 한다
equals가 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다
- equals가 구 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다
- 단, 다른 객체에 대해서 다른 해시값을 반환해야 해시 테이블의 성능이 좋아짐
예시
- 해시 충돌이 더 적은 방법을 꼭 써야한다면 구아바의
com.google.common.hash.Hashing
을 참고
Objects.hash 예시
캐싱 전략: lazy initialization 을 활용한 해시코드
- 스레드 안정성 고려 해야함
- 해싱 비용이 비쌀 시 지연 초기화를 고려할 수 있음
#
11 핵심정리- 핵심필드는 반드시 hashCode 계산 시 빠트리면 안됨
- hashCode 생성 규칙을 javaDocs 에 명시
- 그래야 추후 이해를 돕고 계산 방식을 바꿀 때도 도움
AutoValue
(orLombok
) 프레임워크는 자동으로 equals와 hashCode를 만들어 줌
#
Item 12. toString을 항상 재정의하라#
toString 일반 규약- 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보
- 모든 하위 클래스에서 이 메서드를 재정의하라
#
toString 특징 및 구현- toString을 잘 구현한 클래스는 디버깅이 쉬움
- 그 객체가 가진 주요 정보 모두를 반환하는 것을 권장
- 너무 크다면 요약 정보도 가능
- 표준화 된 값 클래스라면 문서화하기를 권함
- toString이 표현하는 값들에 대한 접근자는 제공하자
- AutoValue (or Lombok) 프레임워크로 자동생성된 toString이 기본 toString 보단 나음
- 표준화된 데이터 (전화번호 등) 을 표현하는 것엔 적합하지 않음
#
toString이 필요없는 사례- 유틸리티 클래스
- 열거 타입
#
12 핵심요약- 무조건 재정의
- 물론 상위 클래스의 정의가 완벽하다면 생략 가능
#
Item 13. clone 재정의는 주의해서 진행하라#
Cloneable 인터페이스- 메소드 하나 없지만, protected인 clone의 동작을 방식을 결정함
- Cleanable 인터페이스를 구현한 객체의 clone을 호출하면 해당 객체의 필드들이 복사된 객체가 반환됨
- 실무에선 Cloenable를 구현한 클래스는 clone 메서드를 public으로 제공함
- 모순적이지만 이렇게 되면 생성자를 통하지 않고 객체를 생성할 수 있게 됨
#
clone 일반 규약x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
(필수는 아님)
#
예시가변 상태를 참조하지 않는 클래스용 clone 메소드
가변 상태 존재 시
#
clone 구현 시 주의 사항- 깊은 복사를 할 수 있도록 해야함
- HashTable.Entry는 clone 메서드가 깊은 복사를 지원함
- 고수준 레벨의 API로 카피할 순 있지만 그럼 추상화 레벨이 안맞아 권장하지 않음
- throws 절을 없애야 함
- 상속용 클래스는 Cloenable을 구현해선 안됨
- Cloenable 인터페이스가 구현된 클래스를 상속해야하는 상황이 어쩔 수 없이 발생한다면, clone을 재정의 해야함
하지만 구현되어 있지 않다면, 복사 생성자나 복사 팩터리라는 더 나은 객체 복사 방식으로 나아갈 수 있음
- 복사(변환) 생성자:
public Yum(Yum yum)
- 복사(변환) 팩터리:
public static Yum newInstance(Yum yum)
- 복사(변환) 생성자:
#
13 핵심 정리- 복제 기능은 생성자와 팩터리를 이용하는게 최고
- 배열은 clone 메서드 방식이 유일하게 잘만듦
- 부모 클래스가 Cloneable 구현체라면 어쩔 수 없이 clone 재정의
#
Item 14. Comparable을 구현할지 고려하라- Compareable 인터페이스의 compareTo 는 Object의 메서드가 아니지만 equals의 비슷한 부분이 많음
- equals와 다른 부분 두가지
- 순서 비교 가능
- 제네릭
- compareTo 구현하면 제네릭과 컬렉션의 힘을 가질 수 있음
#
compareTo 메서드의 일반 규약- 해당 객체와 주어진 객체의 순서를 비교한다 (A.compareTo(B))
- A < B 라면 음의 정수
- A == B 라면 0
- A > B 라면 양의 정수
- 비교할 수 없는 타입이면
ClassCastException
던짐- 보통 다른 타입의 객체이면 해당 예외를 던짐
- 반사성, 대칭성, 추이성이 보장되야 함
(x.compareTo(y) == 0) == (x.equals(y))
- compareTo 메서드로 수행한 동치성 테스트는 equals와 같아야 함
note
compareTo 규약을 지키지 못하면 비교를 활용하는 클래스(컬렉션) 과 어울리지 못함
예시
- 정렬된 컬렉션인 TreeSet, TreeMap
- 검색, 정렬 알고리즘을 활용하는 유틸 클래스 Collections, Arrays
#
주의사항도 equals와 비슷- 필드 추가시 compareTo 에 반영할지 고려
- 물론 반영한다면 OCP 위배..
- 우회법으로 equals와 마찬가지로 해당 클래스를 레퍼런스 인스턴스로 가지는 Wrapper 클래스를 만들고 확장
#
compareTo 작성요령- 제네릭이기 때문에 컴파일 타임에 타입이 정해짐
- 인수 타입을 확인하고 변환할 필요가 없음
- compareTo 작성 중 필드들을 비교하려면 다음과 같다
- Compareable이 구현된 참조 필드는 해당 객체는 compareTo를 활용한다
- 그렇지 않으면, Comparator를 이용한다
- primitive 타입은 wrapper 클래스의
.compare
를 이용한다
- primitive 타입은 wrapper 클래스의
#
compareTo 예시comparator를 이용한 compareTo 구현
wrapper 클래스의 compare 이용 예시
Java 8, 메서드 체이닝을 활용한 Comparator 구현 및 활용
- 성능저하가 약 10%(?) 정도 됨
- 객체 참조용 비교자 생성 메서드도 있음
#
14 핵심정리- 순서를 고려해하는 값 클래스를 작성한다면 Comparable 인터페이스를 구현한다
- 필드 값을 비교할땐
<
,>
와 같은 것을 이용하지 않는다 - Wrapper 클래스의 compare 메서드나 Comparator 를 활용한다.