자바랭 패키지의 Object 클래스가 일반적으로 갖고있는 객체 공통 메소드에 대해 설명한 챕터입니다.
10. equals()는 재정의 해야할때만 재정의
- VO처럼 값이 불변이며, 논리적 동치성을 확인해야하는 객체에만 equals()를 재정의해야한다
- 반사성 및 대칭성(equals 호출 순서 교환 가능), 추이성(3단 논법), 일관성, equals()의 인자가 null일때 false를 반환하는 등의 규약을 따르자
- equals()를 재정의하는 일반적인 순서 (오버라이드 하는 객체를 Object 타입으로 받았을 경우)
- == 연산자로 자기자신임을 확인
- instanceof 연산자로 클래스 동치성 확인, 같으면 형변환
- 동등성을 비교하고자하는 각 필드값의 논리적 동치성 검사.
11. equals()를 재정의하려면 hashCode()도 재정의해야한다
- hash를 사용하는 컬렉션 객체(HashSet, HashMap 등)들은 객체의 hashCode() 결과값을 먼저 비교하게 되기 때문
- 이상적인 해시함수 : 서로다른 객체들을 32bit 정수 범위에 균일하게 분배해야함
- 일반적인 재정의 과정
- 각 필드값에 대해 2번 연산을 수행한 후 3번으로 결과를 종합하여 반환한다.
- 필드값 f가 기본 타입이라면 해당 필드 타입의 박싱 타입인 Type의 Type.hashCode(f)를, 레퍼런스 타입이라면 해당 값의 hashCode() 값을, 배열이라면 각 원소를 별도 필드처럼 다룬 연산 결과를 활용한다.
- 2번의 결과 result에 31을 곱한 뒤, 이전 필드의 계산 결과와 더한다. (31은 관습적으로 써온 hashCode연산 소수)
12. toString()은 객체의 설명을 잘 해줄 수 있도록 재정의
- 디버깅에 사용되는, 로깅에 사용되는 클래스 정보를 잘 나타낼 수 있는 간단하면서도 확실한 수단이 toString()
- 따라서 디버깅에 중요하게 사용될 클래스의 주요 정보들을 가독성있게 toString()에서 반환하는 것이 좋다
- 포맷을 정해놓고, toString() 포맷에서 얻을 수 있는 정보를 뽑는 API를 추가하는 것도 좋음
13. clone() 재정의는 주의해서 진행
- clone()를 통해 복사된 객체가 반환될 것이라고 기대하지만, 생성자 없이 객체를 생성한다는 점 / 클론된 객체가 가변 클래스를 참조했을 때 독립적이지 않은 동작을 할 수 있다는 점 / 상속 관계에서 복잡성이 발생한다는 점 때문에 추천되지 않음
- 인자로 객체를 받아 deepCopy하는 정적 복사 팩토리 메소드를 만드는 방식을 고려
14. 필요할 때 Comparable 구현
- compareTo()는 호출한 객체가 사전순으로 앞설 때 음의 정수를, 뒤에 있을 때 양의 정수를 return
- 순서를 고려해야하는 클래스에 Comparable을 구현하면 TreeSet, TreeMap, Arrays.sort() 등 정렬 기능을 제공하는 컬렉션이나 메소드에 클래스 사용 가능
- 가장 중요한 필드부터 비교해나가면서 compareTo()의 부호 결과 반환하는데, 필드 타입에 따라 다음과 같이 비교
- Primitive Type이라면 Type.compare() 정적 메소드 사용 (<, > 쓰지 말자)
- Reference Type이라면 해당 타입의 compareTo() 사용
- Comparator 인터페이스를 통해 비교자 생성 메소드 사용 가능
독후감
해시 관련 컬렉션 객체들을 이용하면서 equals()를 재정의한다면 hashCode()도 재정의하라는 말을 지겹도록 들었는데 다시 살펴볼 기회가 된 것 같습니다. 사실 이미 한 번 이에 관해 정리한 적도 있음
로깅과 toString() 관련해서, 지금까지 프로젝트에서 로그를 찍을 땐 getter를 통해 해당 상황에서 체크가 필요한 클래스 필드값들을 떼어오는 식으로 로그를 남겼습니다. 예를 들어서, 어떤 사용자가 잘못된 입력을 넣으면 입력 객체에서 잘못된 필드값을 뽑아서 에러 메세지를 구성하는 것과 같은 식으로요.. 객체 자체를 로깅하는 경우는 많지 않았는데, 값 객체나 엔티티 객체에 대해선 재정의를 해야한다는 생각이 듭니다. 물론 제가 속하게 될 팀의 의견?컨벤션? 이 우선이겠지만요.(그래도 건의는 해볼듯 ㅎ)
가장 어려운 부분은 Cloneable관련한 clone() 메소드인데, 사실 clone()을 재정의했을 때 올라가는 복잡도와,, 위험한 이유에 대해.. 책을 읽었음에도 아래와 같은 상태가 되었습니다 ㅜ
clone을 재정의하기 위해선 super 타입의 clone을 호출하고, 형변환을 하고, 나머지 필드를 같게 설정해야 하는데.. 필드값이 배열이나 가변 객체일 경우 클론 객체, 혹은 원본 객체에서 해당 값을 변경했을 때 서로에게 영향을 주는 예상치 못한 결과가 발생할 수 있다 - 라는 점까지만 이해하고 넘어가야겠습니다.