public boolean equals(ClassName other) { // 잘못된 equals!! } // 다음이 올바르다. 무조건 다음과 같이한다. public boolean equals(Object other) { ... }
equals가 사용하는 필드와 hashCode가 사용하는 필드를 동일하게 맞춰야 한다.HashSet.contains 등이 올바르게 작동하지 않게 된다. HashSet은 hashCode 기반으로 bucket을 결정한다. equals(Object) 호출시 true라면 두 객체의 hashCode()는 동일한 int 를 리턴해야 한다.hashCode는 equals에서 사용한 필드들만을 사용해야한다.equals, hashCode를 구현한 상태에서 해당 필드 값을 변경하면 동일 객체라도 hashCode가 바뀌게 된다.
equals는 null이 아닌 객체간에는 항상 동치 관계를 유지하게 구현해야만 한다.
x.equals(x)는 항상 true여야 한다.x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true여야 한다. 이행성 위반 예x.equals(y)를 여러번 호출해도 일관성있게 true를 반환하거나 일관성있게 false를 반환해야 한다.x.equals(null)은 항상 false를 반환해야 한다.
상속 관계에서는 위 동치성을 쉽게 위반하게 된다. canEqual 메소드를 통해 이를 지켜내야 한다.
equals와 hashCode를 override 할 때마다 can equal메소드도 함께 구현하고 오버라이드 해주면 된다.
이를 통해 현재 클래스의 상위클래스와 동등하게 평가되는 것을 막을 수 있다.
// Point 라는 클래스가 존재할 때 public boolean canEqual(Object other) { return (other instanceof Point); } @Override public boolean equals(Object other) { boolean result = false; if (other instanceof Point) { Point that = (Point) other; // that.canEqual(this) 가 핵심이다. 이를 거꾸로 this.canEqual(that) 으로 사용하면 안된다. result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY()); } return result; } // 오버라이드 되는 ColoredPoint 클래스에서는 @Override public boolean equals(Object other) { boolean result = false; if (other instanceof ColoredPoint) { ColoredPoint that = (ColoredPoint) other; result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that)); } return result; } // that.canEqual(this) 호출을 통해서 상위클래스(Point)는 결코 ColoredPoint와 같을 수 없게 보장됨. @Override public boolean canEqual(Object other) { return (other instanceof ColoredPoint); }
canEqual이 구현되어 있는 경우 하위 클래스에서 canEqual을 구현하거나 하지 않음으로써 상위클래스와의 동등성 비교를 허용하거나 안할 수 있다.false가 될 가능성이 높은 잘못된 코딩이다.