아이템 10. equals는 일반 규약을 지켜 재정의하라
일반적으로 equals를 재정의하지 않는 것이 가장 좋다.
🔸 equals를 재정의 하지 않아도 될 때
- 각 인스턴스가 본질적으로 고유할 때 - Thread
- 싱글톤 인스턴스
- Enum
🔸 equals를 재정의 할 때 지켜야 할 규약 (null이 아닌 참조 값)
- 반사성 : x.equals(x)는 true이다.
- 대칭성 : x.equals(y)는 true이다.
- 추이성 : x.equals(y)가 true이고, y.equals(z)가 true이면, x.equals(z)는 true이다.
- 일관성 : x.equals(y)를 반복해서 호출하면 항상 같은 값을 반환한다.
- null-아님 : x.equals(null)은 false이다.
구체 클래스를 확장해 새로운 값을 추가하면서 equasl 규약을 만족시킬 방법은 존재하지 않는다.
🔸 instanceof는 null 검사를 명시적으로 하지 않아도 된다.
❌ Bad - 명시적 null 검사 - 필요 없다.
@Override
public boolean equals(Object o) {
if (o == null)
return false;
...
}
✅ Good - 무시적 null 검사
@Override
public boolean equals(Object o) {
if (!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
...
}
🔸 equals 재정의 방법
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인
- instanceof 연산자로 입력이 올바른 타입인지 확인
- 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 확인
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
...
}
🔸 타입별 비교 방법
- 기본 타입 필드(float, double 제외)는 == 비교
- 참조 타입 필드는 equals 비교
- float, double은 정적 메서드인 Float.compare, Double.compare로 비교한다. (Float.equals나 Double.equals는 성능상 좋지 않으므로 지양하자)
- null도 정상 값으로 취급하는 참조 타입 필드의 경우 Objects.equals(Object, Object)로 비교
equals를 작성하고 테스트하는 일은 지루하다. AutoValue 프레임워크를 사용하자.
꼭 필요한 경우가 아니라면 equals를 재정의하지 말자. 많은 경우 Object의 equals가 원하는 비교를 수행해준다.
아이템 11. equals를 재정의하려거든 hashCode도 재정의하라
equals를 재정의할 때는 hashCode도 반드시 재정의 해야 한다.
그렇지 않으면 일반 규약을 어겨 HashMap 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 수 있다.
서로 다른 인스턴스라면 되도록 해시코드는 다르게 구현해야 한다. 역시 AutoValue 프레임워크를 사용하면 자동으로 만들어준다.
아이템 12. toString을 항상 재정의하라
🔸 toString 작성법
- toString은 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.
- toString의 반환값의 포맷을 문서화할지 정하자.(포맷을 명시하든 안하든 의도는 명확히 밝혀야 한다.)
- toString이 반환한 값에 포함된 정보를 얻을 수 있는 접근자를 제공해야 한다.
모든 구체 클래스에서 Object의 toString을 재정의하자.
toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야 한다.
toString을 잘 구현한 클래스는 디버깅하기도 쉽다.
아이템 13. clone 재정의는 주의해서 진행하라
clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다. => 배열의 clone을 재귀적으로 호출하는 방법
13-2. 가변 상태를 참조하는 클래스용 clone 메서드
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
복잡한 clone 구현을 해야 할까? Cloneable을 이미 구현한 클래스를 확장하는 것이 아니라면 복사 생성자와 복사 팩토리 방식으로 더 나은 객체 복사 방식을 제공할 수 있다. (단, 배열만은 clone 메서드 방식이 가장 깔끔하다.)
//복사 생성자
public Yum(Yum yum) {...};
//복사 팩토리
public static Yum newInstance(Yum yum) {...};
아이템 14. Comparable을 구현할지 고려하라
물리적 동치성 : 스택의 값 비교(참조형: 주소값, 기본형: 값)
논리적 동치성 : 실제 가지는 값 비교(참조형: Heap의 값, 기본형: 값)
Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다.
자바 플랫폼의 모든 값 클래스와 열거 타입이 Comparable을 구현했다. 알파벳, 숫자, 연대 같이 숫서가 명확한 값 클래스를 작성한다면 반드시 Comparable을 구현하자.
🔸 정렬된 컬렉션들은 동치성을 비교할 때 equals 대신 compareTo를 사용한다. (BigDecimal처럼 compareTo와 equals가 일관되지 않는 경우도 있기에 주의하자.)
🔸 compareTo 작성 요령
- compareTo 입력 인수의 타입을 확인하거나 형변환할 필요 없다. (어차피 컴파일 타임에 걸린다.)
- compareTo는 각 필드가 동치인지를 비교하는게 아니라 그 순서를 비교한다.
- 정수,실수의 비교시 박싱된 기본 타입의 정적 메서드 compare를 이용하자(<,>는 java7 이전 정수 비교시)
- java8 부터는 Comparator 인터페이스와 원하는 비교자 생성 메서드를 사용하여 compareTo 메서드를 간결하게 구현할 수 있다.
🔸 Comparator의 수많은 보조 생성 메서드들
- 자바의 모든 숫자용 기본 타입을 커버한다.
- 객체 참조용 비교자 생성 메서드도 준비되어 있다.
필드의 값을 비교할 때 <와 > 연산자는 쓰지말아야 한다. 박싱된 기본 타입 클래스의 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.
'기술서적' 카테고리의 다른 글
EFFECTIVE JAVA3 - 2장. 객체 생성과 파괴 (0) | 2022.04.15 |
---|---|
Clean Code - #3장. 함수 (0) | 2022.04.08 |
Clean Code - #2장. 의미 있는 이름 (0) | 2022.04.07 |