equals를 재정의하면 안되는 경우
- 각 인스턴스가 본질적으로 고유한 경우: 값을 표현하는 것이 아닌 동작하는 개체를 표현하는 경우
- 인스턴스의 '논리적 동치성'을 검사할 일이 없는 경우
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 적용 가능한 경우
- private 또는 packaage-private(default) 클래스이면서 equals메서드를 호출하지 않는 경우
그럼 언제 equals를 재정의할까요?
객체 식별성이 아니라 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals로는 불가능한 경우. 주로 값 클래스들이 여기에 해당합니다.
equals를 재정의할 때 지켜야 하는 규약
equals 메서드는 null이 아닌 개체 참조에 대해 등가 관계를 구현
null이 아닌 모든 참조값 x,y,z에 대하여반사성(reflexity): x.equals(x) == true
대칭성(symmetry): x.equals(y) == true => y.equals(x) == true
추이성(transitivity): x.equals(y) == true && y.equals(z) == true => x.equals(z) == true
일관성(consistency): x.equals(y)를 언제 호출해도 항상 같은 결과를 반환
x.eqauls(null) == false
반사성(reflexity)
객체는 자기 자신과 같아야 합니다. 이를 성립하지 않는 경우 컬렉션에서 contains메서드를 호출해도 찾을 수 없을 것 입니다.
대칭성(symmetry)
두 객체는 서로에 대한 동치여부가 똑같아야 합니다. (x.equals(y) == true => y.equals(x) == true)
public class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
}
if (o instanceof String) { //얘 때문에 잘못됨
return s.equalsIgnoreCase((String) o);
}
return false;
}
}
위의 경우
CaseInsensitiveString cis = new CaseInsensitiveString("TEST");
System.out.println(cis.equals("test")); //true
System.out.println("test".equals(cis)); //false
CaseInsensitiveString는 String 클래스인 경우도 생각하지만(if (o instanceof String)
) String클래스는 CaseInsensitiveString클래스는 모르기 때문에 대칭성이 성립하지 않습니다.
대칭성이 성립하지 않는 경우 컬렉션의 contains메서드를 호출했을 때 JDK 구현에 따라 true/false/런타임 예외를 반환합니다.
추이성(transitivity)
쉽게 삼단논법을 생각하면 됩니다. (A=B이고 B=C이면 A=C)
하위 클래스에서 상위 클래스에는 없는 새로운 필드를 추가하는 경우 어기기 쉽습니다.
public class Point2D {
private final int x;
private final int y;
public Point2D(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point2D)) return false;
Point2D p = (Point2D) o;
return p.x == x && p.y == y;
}
}
public class Point3D extends Point2D {
private final int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point3D)) return false;
return super.equals(o) && ((Point3D) o).z == z; //잘못됨
}
}
이 경우
Point2D point2D = new Point2D(1, 2);
Point3D point3D = new Point3D(1, 2, 3);
System.out.println(point2D.equals(point3D)); //true
System.out.println(point3D.equals(point2D)); //false
이럴 떄는 상속 대신 컴포지션을 사용합니다.
public class Point3D {
private final Point2D point;
private final int z;
public Point3D(int x, int y, int z) {
point = new Point2D(x, y);
this.z = z;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point3D)) return false;
Point3D p = (Point3D) o;
return p.point.equals(point) && p.z == z;
}
}
일관성(consistency)
equals의 판단에 신뢰할 수 없는 자원이 있어서는 안됩니다.
URL url1 = new URL("https://sinau.tistory.com");
URL url2 = new URL("https://sinau.tistory.com");
System.out.println(url1.equals(url2));
이 경우 주어진 URL과 매핑된 호스트의 IP주소를 이용해 비교하는데 이 때문에 항상 같은 결과가 나온다는 보장이 없습니다.
null-아님
o.equals(null)은 항상 false를 반환해야 하며 NullPointerException도 허용하지 않습니다. 그렇다고 항상 null검사를 할 필요는 없습니다.instanceof
는 첫 번째 피연산자가 null이면 false를 반환하기 떄문에 명시적으로 null검사를 할 필요가 없습니다.
좋은 equals 구현방법
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인, 성능 향상용
- instanceof 연산자로 입력이 올바른 타입인지 확인
- 올바른 타입으로 형변환
- 핵심 필드들이 모두 일치하는지 확인
- float와 double을 제외한 기본타입필드는 ==으로 비교
- float는 Float.compare(float, float), double은 Double.compare(double, double)로 비교
- Float.NaN, -0.0f 와 같은 특수한 부동소수 값을 다뤄야하기 때문
- Float.equals, Double.equals는 오토박싱을 수반할 수 있으므로 성능상 좋지 않습니다.
- 배열의 모든 원소가 핵심필드라면 Arrays.equals를 사용
- null도 정상 값으로 취급하는 경우 Objects.equals(Object, Object)를 이용해 NPE 발생을 예방
- 어떤 필드를 먼저 비교하느냐가 성능에 영향을 끼치므로 비교순서를 잘 생각하여 구성
- eqauls를 재정의할때는 반드시 hashCode도 재정의!
'스터디 > 이펙티브 자바' 카테고리의 다른 글
[Effective Java] Item12. toString을 항상 재정의 (0) | 2022.05.21 |
---|---|
[Effective Java] Item11. equals재정의시 무조건 hashCode도 재정의 (0) | 2022.05.21 |
[Effective Java] Item 9. try-finally보다는 try-with-resource (0) | 2022.05.15 |
[Effective Java] Item 8. finalizer와 cleaner 사용을 피하라. (0) | 2022.05.15 |
[Effective Java] Item 7. 다 쓴 객체를 참조 해제하라. (0) | 2022.05.15 |