2024. 3. 10. 17:03ㆍJava/Effective Java
1. equals 규약을 지키면서 값을 추가하기
equals를 override할 때에는 반드시 아래의 일반 규약을 만족해야 합니다.
(아래 규약은 모두 null이 아닌 참조값 x에 대한 규약입니다)
- x.equals(x) 는 반드시 true 여야 하고
x.equals(y) == true
라면,y.equals(x) == true
여야 합니다.x.equals(y) == true 이고, y.equals(z) == true
라면,x.equals(z) == true
여야 합니다.x.equals(y)
결과는 여러번 호출하더라도 늘 같은 값을 반환해야 합니다.x.equals(null)
은 반드시 false 여야 합니다.
위의 특징을 고려했을 때, 어떤 구현클래스의 상속클래스는 이 규칙을 만족하지 못하게 됩니다.
예를 들어, 아래와 같은 Point 객체가 있고,
@RequiredArgsConstructor @Getter public class Point { private final int x; private final int y; @Override public boolean equals(Object o) { if(o instanceof Point p) { return this.x == p.x && this.y == p.y; } return false; } }
이를 상속한 NamePoint 클래스가 있다고 할 때,
@Getter public class NamePoint extends Point { private final String name; public NamePoint(int x, int y, String name) { super(x, y); this.name = name; } @Override public boolean equals(Object o) { if(o instanceof NamePoint p) { return super.equals(p) && this.name.equals(p.name); } return false; } }
여기서 정의된 eqauls 메서드는 일반 규약을 만족하지 않습니다.
point를 namePoint와 비교한 경우에는 결과값이 true지만, 순서를 바꿀 경우 equals 결과가 달라지 기 때문입니다.
Point point = new Point(2, 3); NamePoint namePoint = new NamePoint(2, 3, "coco"); System.out.println(point.equals(namePoint)); // true System.out.println(namePoint.equals(point)); // false
객체 지향적 추상화의 이점을 이용하면서, 구체 클래스를 확장해 새로운 값을 추가하면서 equals 규약을 만족시킬 수 있는 방법은 없습니다
equals 규약을 지키면서 값을 추가하고 싶다면
- 구체 클래스의 하위 클래스에 값을 추가하는 것(상속)이 아닌, 컴포지션을 사용 하거나
- 최상위에 추상클래스를 정의하고, 하위클래스에서 값을 추가하면 됩니다.
composition 사용 예제
구체 클래스의 하위 클래스에 값을 추가하는 것(상속)이 아닌, 컴포지션을 사용 하면 equals 규약을 지키면서 값을 추가할 수 있습니다.
@Getter @RequiredArgsConstructor public class Point { private final int x; private final int y; @Override public boolean equals(Object o) { if(o instanceof Point p) { return this.x == p.x && this.y == p.y; } return false; } }
@Getter @RequiredArgsConstructor public class ColorPoint { private final Color color; private final Point point; public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (o instanceof ColorPoint cp) { return this.point.equals(cp.point) && this.color.equals(cp.color); } return false; } }
또는, 최상위에 아무런 필드값을 갖지 않은 추상클래스로 정의하여 상위 클래스를 직접 인스턴스로 만드는게 불가능하다면 상속 개념을 이용하면서 equals 규약을 만족시킬 수 있습니다.
2. 기타 고려 사항
2.1. float, double 비교
부동소수 값을 다루는 float, double 타입의 비교는 정적메서드 Float.compare(f1, f2), Double.compare(d1, d2) 로 비교하여 equals 체크합니다.
Float.equals, Double.equals는 primitive 타입일 경우 auto boxing이 일어나기 때문에 성능이 더 떨어집니다.
2.2. null도 값으로 취급하는 참조타입 비교
null도 정상값으로 취급하는 참조타입이라면, Object.equals(o1, o2) 로 비교하여 NullPointerException 발생을 방지합니다.
또, 성능을 높이고 싶다면 가장 다를 가능성이 큰 필드부터 비교하도록 하면 좋습니다.
2.3. Object 이외의 타입을 매개변수로 받는 equals는 사용하지 말자
매개변수를 Object가 아닌 다른 값으로 넣는 것은, 기본 equals 매서드를 재정의하는 것이 아닌 동일한 메서드명으로 메서드를 overload 한 것입니다.
public boolean equals(Point p) { ... }
오버라이딩 할 메서드를 정의할 때, @Override 애너테이션을 붙여준다면, 애초에 컴파일시 에러가 발생되기 때문에 오류를 방지할 수 있습니다.
그러니, @Override 애너테이션을 붙여 실수를 예방합시다.
@Override public boolean equals(Object o) { if(o instanceof Point p) { return this.x == p.x && this.y == p.y; } return false; }
3. equals 재정의 후 hashCode도 재정의하라
equals를 재정의 한 후, hashCode를 재정의하지 않으면, HashMap같은 컬렉션에 요소를 넣을 때 문제가 발생됩니다.
두 객체의 equals 결과가 true라면 hashCode 결과도 같아야 합니다.
클래스가 불변 클래스면서 해시코드 계산 비용이 큰 경우라면, 지연 초기화 전략으로 hashCode를 읽어들이는 것도 좋은 방법입니다.
private int hashCode; @Override public int hashCode() { int result = hashCode; if(result == 0) { // 연산식 } return result; }
어느정도 성능도 고려하면서, 동일 객체 여부를 잘 판단하는 hashCode를 작성하는 것이 중요한데,
Lombok 을 이용하면 이런 부분을 매우 간편하게 해결할 수 있습니다.
@EqualsAndHashCode @RequiredArgsConstructor @Getter public class Point { private final int x; private final int y; }
지연 초기화 캐시전략도 지원합니다.
@EqualsAndHashCode(cacheStrategy = EqualsAndHashCode.CacheStrategy.LAZY)
++
- 아이템 10. equals는 일반 규약을 지켜 재정의하라
- 아이템 11. equals를 재정의하려거든 hashCode도 재정의하라
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 15. 상수로 사용하고자하는 필드값이 불변객체인지 고려해야한다 (0) | 2024.03.16 |
---|---|
[Effective Java] Item 14. Comparable을 구현할지 고려하라 (0) | 2024.03.10 |
Item 9. close 처리해야하는 resource는 try-with-resource를 이용하자 (0) | 2019.03.12 |
Item 6. 불필요한 객체 생성을 피하자 (0) | 2019.03.12 |
Item 3~5. private 생성자 또는 열거타입으로 Singleton 보증하라 (0) | 2019.03.10 |