머리말
객체가 공통으로 사용하는 메서드가 무엇이 있고, 어떻게 다뤄야하는지 알아보는 시간을 갖도록 하겠습니다.
Object에서 제공하는 equals(), hashCode(), toString(), clone(), finalize()는 모두 재정의(overriding)을 염두해 두고 설계한것이다. HashMap, HashSet에서 해당 Object를 사용하려면 equals()와 hashCode()를 오버리이딩 해서 구현해야한다.
Compareable 인터페이스는 CompareTo()를 구현해야하는데 이부분은 배열의 정렬이나, TreeMap, TreeSet에서 해당 Object를 사용하려면 구현을 해야한다.
equals는 일반 규약을 지켜 재정의 하라
- 자기자신은 true를 반환해야한다.
- 해당인스턴스가 아니라면 false를 반환해야한다.
- 핵심필드에 대하여 비교 연산을 수행한다.
equals는 아래의 성질을 다 만족해야한다.
- 반사성(reflexivity) : x.equals(x) : 모든 참조값 x에 대하여 true 이어야한다.
- 대칭성(symmetry) : x.equals(y) == y.equals(x), x가 y와 같다면 y도 x와 같아야한다.
- 추이성(transitivity) : x.equals(x) == y.equals(x) && y.equals(z) == z.equals(y) 라면 x.equals(z) 이어야한다.
- 일관성(consistency) : x.equals(y)를 반복해서 사용해도 항상 같은 값을 반환해야한다.
- null-아님 : null이 아닌 모든 참조 값 x에 대하여 x.equals(null)은 항상 false이다.
핵심정리
<aside> ✏️ 꼭 필요한 경우가 아니라면 equals를 재정의 하지말자, 많은 경우에 object의 equals가 여러분이 원하는 비교를 정확히 수행해준다. 재정의해야 할 때는 그 클래스의 핵심 필드를 모두 빠짐없이, 다섯가지 규약을 확실히 지켜가며 비교해야한다.
</aside>
equals를 재정의한 클래스에서 hashCode도 재정의 하라
만약, hashCode를 정의하지 않는다면 HashMap이나 HashSet 같은 컬랙션 원소로 사용할때 문제가 발생한다. 논리적으로 같은 객체는 같은 해시코드를 반환해야한다.
모든 객체에 똑같은 해시코드를 반환하면, 버킷하나에 담겨 마치 연결 리스트(linked list)처럼 동작한다. 그결과 평균 수행시간이 O(1)인 해시테이블이 O(n)으로 느려져서, 객체가 많아지면 쓸수 없다.
hashCode를 만드는 방법 : 모든 핵심 필드들의 hashCode값을 가져와 재귀적으로 더한다.
또한 hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 말자. 그래야 클라이언트가 이 값이 의존하지 않게 되고, 추후 계산 방식을 바꿀 수 있다.
핵심정리
<aside> ✏️ equals를 재정의 할 때에는 반드시 hashCode도 재정의해야한다. 그렇지 않으면 프로그램이 재대로 동작하지 않을 것이다. Google에서 만든 라이브러리 @Autovalue 프레임워크를 사용하여 equals와 hashCode를 자동으로 만들어준다.
</aside>
toString을 항상 재정의하라
toString을 잘 정의한 Object는 사용하기 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅 하기도 쉽다.
toString이 반환값에 포함된 정보를 얻어 올 수 있는 API를 제공하자.
toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야한다.
clone을 재정의는 주의해서 진행하라
Clonable인터페이스는 clone을 구현해야한다. Object 객체는 기본적으로 protected이다.
Cloneable인터페이스를 구현한 객체가 clone을 호출하면 그 객체와 똑같은 객체를 반환하고, 만약 구현되어있지 않다면 ClonenotSupportedException이 발생한다.
- clone을 구현할때 공변타이핑(convariant return typing)을 사용하여 반환하자. ( 자기자신의 타입을 반환한다 )
- 객체의 불변을 유지하기위하여 **깊은복사(Deep Copy)**를 사용하여 복사하자.
- 복사 생성자와 복사 팩터리를 사용하여 복사 방식을 제공 할 수 있다.
핵심정리
<aside> ✏️ 기본원칙은 복사 생성자와 복사 팩터리를 사용하고 다만, 배열의 경우는 clone을 사용한다.
</aside>
Compareable을 구현할지 고려하라
동치성을 갖는 Compareable은 equals와 다르게 순서를 보장한다. 검색, 극단값 계산, 자동 정렬되는 컬랙션 관리에도 쉽게 할 수 있다.
compare to 메서드의 일반 규약은 equals와 비슷하다. 반사성, 대칭성, 추이성, 일관성을 보장해야한다.
compareTo 메서드에서 관계 연산자 <와 > 를 사용하는 방식은 거추장스럽고 오류를 발생하니, 이제는 추천하지 않는다. 박싱된 기본 타입 클래스에 새로 추가된 정적 메서드인 compare를 사용하면된다.
핵심정리
<aside> ✏️ 순서를 고려해야 하는 클래스라면 꼭 Compareable 인터페이스를 구현하고, 쉽게 비교하고 정렬하고 비교 기능을 제공하는 컬랙션와 어우러져 사용하게 해야한다. 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Compareator 인터페이스가 제공하는 비교 생성자 메서드를 사용하자.
</aside>
회고
이번장은 언어에서 제공하는 equal, hashCode, toString, clone, compareTo 메서드를 어떻게 사용해야하는지 왜 그렇게 사용해야하는지에 대하여 알아보았습니다. 지금 현재 알고리즘을 공부하고 있어서 compareTo, equal, hashCode에 좀더 집중을 할 수 있었는데요, 순서나 유일성이 보장되는 컬랙션을 사용할때 해당 메서드들을 잘 구현해야 되는것을 더 상기 시키는 시간이 되었습니다.
만약 clone을 사용하는 객체를 구현하고자 한다면, 객체의 불변성을 유지시키는 깊은 복사로 구현을 해야됩니다. 얕은 복사로 복사가 될 경우 그 객체의 값이 언제 어디서 어떻게 바뀔지 추적하기 힘들기 때문입니다. 이건 함수형 프로그래밍에서 아주 중요하게 다루는 내용이라서 눈이 한번 더 갔습니다.
'이팩티브 자바' 카테고리의 다른 글
열거 타입과 에너테이션 (0) | 2024.03.17 |
---|---|
제네릭 (0) | 2024.03.17 |
클래스와 인터페이스 (0) | 2024.03.17 |
객체 생성과 파괴 (0) | 2024.03.10 |