equals
메소드를 정의하기 생각보다 쉽지 않다. 재정의하지 않는 것이 최선이지만 재정의가 필요한 상황에서는 아래 가이드에 따라 정의해보자!
값을 표현하는 것 아니라 동작하는 개체를 표현하는 클래스일 경우
Thread
Object.equals()
Bean
에 등록해두는 객체 repository
, controller
, service
가 이에 해당Object.equals()
만으로도 해결이 가능한 경우
equals
가 하위 클래스에도 딱 들어맞는 경우equals
를 상속받아 사용하는 것을 권장Set
, Map
, List
의 경우 AbstractList
로 부터 equals
상속 받아 사용public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
}
위의 코드와 같이 List
의 대부분은 equals
메소드가 재정의 되어있고 이걸 사용한다.
❗
equals
가 실수로라도 호출되는 것을 막고 싶다면 아래와 같이 하는 것이 좋다
@Override
public boolean equals (Object object) {
throw new AssertionError(); //equals 호출시 error()
}
Enum
💡 객체 식별성 (메모리 상에 같은 위치에 잇는지)이 아니라 논리적 동치성을 확인해야하는데 사우이 클래스의
equals
가 논리적 동치성 비교가 아닐 때 사용해야한다.
Integer
, String
등의 값 클래스 (객체가 같은지가 아니라 값이 같은지를 알기 위해서)💡 동치관계 : 집합을 서로 같은 원소들로 이루어진 부분집합으로 나누는 연산
x.equals(x) == true
contains
메소드를 호출해 true가 나오는지 확인public static void main(String[] args) {
List<Member> members = new ArrayList<>();
Member member = new Member("rutgo", 29);
members.add(member);
System.out.println(members.contains(member)); // true
}
x.equals(y) == y.equals(x)
// CaseInsensitiveString 클래스
class CaseInsensitiveString {
...
@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("Polish");
String s = "polish";
cis.equals(s); // true
s.equals(cis); // false
String
클래스의 equals
는 대소문자 구별하도록 구성되어있기 때문에 해당 부분은 대칭성을 명백히 위반
if(o instanceof String)
->
if(o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s)
이렇게 변경하면 대칭성을 위반하지 않을 수 있다.
x.equals(y) == true
y.equals(z) == true
x.equals(z) == true
equals
를 재정의했을 경우 equals
를 재정의하면 안된다.class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
}
해당 Point 객체를 확장하여 color라는 필드를 추가한다면 어떤 상황이 벌어질까?
이러한 상황이라면 색상 정보는 무시한 채 부모 클래스인 Color
의 equals()
를 사용하여 비교하게 된다.
대칭성이 깨진다.
class ColorPoint extend Point {
private final Color color;
public ColorPoint (int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Point))
return false;
if(!(o instanceof ColorPoint))
return o.equals(this);
return super.equals(o) && ((ColorPoint) o).color == color;
}
}
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
p1.equals(p2); // true
p2.equals(p3); // true
p1.equals(p3); // false
대칭성이 깨지는 상황을 막기 위해 ColorPoint 까지 확인하도록 equals()
를 수정하면 이 부분에서 추이성을 위반하게 된다.
또한, 무한 재귀에 빠질 위험도 있다.
❗ 구체클래스를 확장해 새로운 값을 추가하면서
equals
규약을 만족시킬 방법은 존재하지 않는다.
그러나, 상속 대신 컴포지션을 사용하면 만족할 수 있다.
추상 클래스의 하위 클래스에서는equals
규약을 지키면서 값을 추가 할 수 있다.
x.equals(y) == true;
x.equals(y) == true;
x.equals(y) == true;
equals
의 판단에 신뢰할 수 없는 자원이 끼워들어서는 안된다.x.equals(null) == false
null
과 같지 않아야한다는 뜻NullPointException
을 던지는 경우조차 허용하지 않는다.@Override
public boolean equals(Object o) {
if (!(o instanceof Member)) {
return false;
}
Member member = (Member)o;
return email.equals(member.email) && name.equals(member.name) && age.equals(member.age);
}
위의 예시와 같이 equals()
재정의 시 타입 검사를 하게 된다면 쉽게 지킬 수 있다.
==
연산자를 사용하여 입력이 자기 자신의 참조인지 확인한다.instanceof
연산자로 입력이 올바른 타입인지 확인한다.non-null
체크 가능Collection
)instacneof
검사를 했기 때문에 100% 성공한다.==
으로 비교하고 그 중 double
, float
는 Double.compare()
, Float.compare()
을 이용해 검사해야 한다.Arrays.equals
메서드들 중 하나를 사용하자.hashCode
도 반드시 재정의하자.Object
외의 타입을 매개변수로 받는 equals
메서드는 선언하지 말자. 이는 재정의가 아니라 다중 정의한 것이다.