제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.
2007년 12월 11일
java.lang.Object 메쏘드 분석 2 - equals
equals(Object obj) 는 객체의 레퍼런스가 아닌 객체의 내용이 같은 지를 판단하는 메쏘드입니다. equals 메쏘드는 우리가 모르는 사이에 많이 호출됩니다. 그렇기 때문에 일반적인 클래스를 만들 때 항상 override해주어야 합니다. equals 메쏘드를 override하지 않아 생기는 문제는 찾기가 굉장히 어렵습니다.
equals 를 제대로 구현하는 것은 생각보다 어렵습니다. 얼만큼 어려운가하면, java api에도 잘못 구현된 것들이 종종 있을 만큼 어렵습니다.
equals는 어떤 오브젝트가 인자로 받은 다른 오브젝트와 그 내용이 일치하는 지를 판단합니다. == 부호로 판단하는 것은 레퍼런스, 즉 메모리 상에 같은 객체인지를 판단하는 반면, equals는 그 내용을 판단합니다.
ex>
String a = new String("a");
String b = new String("a");
와 같이 a,b 를 정의할 경우 a==b 는 false를 리턴하지만, a.equals(b)나 b.equals(a)는 true를 리턴합니다.(new String 하는 식으로 String을 생성했다고 뭐라고 하지 마십쇼~ 그냥 예젭니다.)
java api에 있는 equals 구현에 대한 규약을 한글로 설명해 보도록하지요. 번역이 아니라 설명입니다. 쌩으로 번역만 해봤자 정확히 이해하고 넘어가기가 쉽지가 않는 부분들이 좀 있기 때문에 설명으로 합니다.
1. 인자가 null일 경우에는 false를 리턴합니다. api문서에는 아래 설명할 사항들에 대해서 "인자가 null이 아닌 경우에는" 이라는 설명을 달아 놓았지만, 일단 인자가 null이면, false를 리턴하기 때문에 저는 아래 설명에서는 모든 인자가 null이 아니라고 가정하고 설명하겠습니다.
2. 어떤 객체 x에 대해서도 x.equals(x)는 반드시 true를 리턴해야 합니다.
규약 중에 가장 쉬운 부분입니다. 메모리 상에 같은 레퍼런스를 가지면, true를 리턴해야 한다는 것입니다.
ex>
A x = new A();
A y = x;
와 같이 어떤 타입의 A에 대해서도 x와 y는 같은 레퍼런스를 가지기 때문에 x.equals(y)는 true를 리턴해야 합니다.
3. 대칭성. 어떠한 x,y에 대해서도 x.equals(y) 와 y.equals(x)는 같은 값을 가져야 합니다.
이 규약은 참 쉽고 당연한 듯하지만, 지키기가 생각보다 쉽지 않습니다.
잘못 구현된 코드를 잠시 보여드리도록 하지요.
ex>
public class Wrong {
private String name;
public void setName(String name){
this.name = name;
}
@Override
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj instanceof Wrong){
return name.equals(((Wrong)obj).name);
} else {
return false;
}
}
}
위와 같은 방법으로 일반적인 구현을 합니다. 그러나 저런 방식으로 구현을 하게되면, 대칭성을 깨게 됩니다.
Wrong을 상속 받는 WrongChild 클래스의 예제를 보세요.
public class WrongChild extends Wrong{
private final int age;
public WrongChild(String name, int age){
setName(name);
this.age = age;
}
@Override
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj instanceof WrongChild ){
return super.equals(((WrongChild)obj)) && age == ((WrongChild)obj).age;
} else {
return false;
}
}
}
public class Test {
public static void main(String[] args) {
Wrong a= new Wrong();
a.setName("wrong");
WrongChild b= new WrongChild("wrong", 1);
System.out.println(a.equals(b));
System.out.println(b.equals(a));
}
}
a.equals(b)는 어떤 값을 리턴할까요? b는 Wrong의 인스턴스가 맞습니다. 그러므로, name 이라는 멤버변수를 비교합니다. 역시 같은 값입니다. 당연히 true를 리턴합니다.
b.equals(a)는 어떻게 될까요? a는 WrongChild의 인스턴스가 아닙니다. false가 리턴이 됩니다.
대칭성은 이렇게 쉽게 깨질 수 있습니다. 상속이 가능한 클래스에서는 equals를 구현하는 데 있어서 instanseof 만으로 타입을 비교해서는 안 됩니다. 이는 대칭성을 깨는 지름길입니다. 그러나 아쉽게도 우리가 보는 많은 클래스들이 이런 식으로 구현이 되어있습니다.
다만 예외적인 경우는 있습니다. 데이터의 집합체의 경우가 대표적입니다. 예를들어, Map의 구현체인 HashMap, Hashtable 또는 TreeMap과 같은 경우는 equals를 구현하는데 있어서 인자의 타입이 크게 중요하지 않습니다. 그 속에 들어있는 내용이 무엇인가가 중요한 것이죠. anHashMap.equals(anHashtable)가 true를 반환할 수도 있는 것입니다.(여기서 anHashMap은 HashMap의 인스턴스, anHashtable은 Hashtable의 인스턴스입니다.) 실제로 소스를 보면, HashMap은 상위 클래스인 AbstractMap에 구현되어 있는 equals를 호출하고, Hashtable은 자신의 equals를 호출하는데, 그 둘의 구현은 같습니다. 둘 모두 상위 인터페이스인 Map으로 캐스팅을 한 후 Map의 key를 iterator로 돌면서 value를 비교하는 식으로 구현되어 있습니다.
그러면 제대로 된 대칭성을 구현할려면 어떻게 해야 할까요?
getClass()메쏘드를 써야 합니다. 어떤 클래스의 타입은 그 클래스를 상속받은 클래스의 타입과는 다릅니다.
황토색 부분은
if(getClass() == obj.getClass())와 같이 바꾸어 주어야 제대로 작동합니다.
4. 추이성: 어떤 x,y,z에 대해서도 x.equals(y)와 y.equals(z)가 true면 x.equals(z)도 true를 리턴해야 합니다.
대칭성과 마찬가지로 말은 참 뻔해 보이지만 구현이 생각보다 쉽지 않습니다. 삑사리는 대칭성과 마찬가지로 상속의 경우에 발생하게 됩니다. 추이성을 보장하는 방법은 인자가 자기 자신의 클래스 타입이 아닐 경우 무조건 false를 리턴하고, 대칭성을 명확히 지켜주면 됩니다.
그러나 위에 설명드린 바와 같이 데이터의 집합체와 같이 instanceof로 equals 메쏘드를 구현할 경우는 좀 더 깊이 생각해볼 필요가 있습니다.
5. 일관성: x,y의 멤버 변수가 변하지 않는 이상 x.equals(y)는 항상 같은 값을 리턴해야 한다.
머 이건 뻔합니다. 딱히 설명할 것도 없구요. equals 내부에서는 멤버 변수의 값들을 가지고 비교를 해야 한다는 것입니다.
이 글과 관련있는 글을 자동검색한 결과입니다 [?]
- java.lang.Object 메쏘드 분석 3 - hashCode by 삼실청년
- java.lang.Object 메쏘드 분석 4 - clone by 삼실청년
- java.lang.Object 메쏘드 분석 1 - getClass by 삼실청년
- java의 synchronized 분석 by 삼실청년
- 레퍼런스 변수 #2 by 다키짱
# by | 2007/12/11 02:26 | 컴터질~ | 트랙백 | 덧글(0)





☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]