🏳🌈 흔히 equals()
두객체의 내용이 같은지 확인하는 메소드, hashCode()
두 객체가 같은 객체인지 확인하는 메소드 로 알고있다.
기본 개념에 대해서 더 자세하게 정리해 보도록 하자.
클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object
클래스를 상속하게 된다.
따라서 자바의 모든 클래스는 Object 클래스의 자식이거나 자손 클래스이다.
Object는 자바의 최상위 부모 클래스
에 해당한다.
Object 클래스는 필드가 없고, 메소드들로 구성되어 있다. 이 메소드들은 모든 클래스가 Object를 상속하기 때문에 모든 클래스에서 사용이 가능하다.
주로 clone(), equals(), hashCode(), toString() 등을 사용한다.
//Object의 equals() 메소드
public boolean equals(Object obj) {…}
equals() 메소드의 매개 타입은 Object인데, 이것은 모든 객체가 매개값으로 대입될 수 있음을 말한다. 그 이유는 Object가 최상위 타입이므로 모든 객체는 Object 타입으로 자동 타입 변환될 수 있기 때문이다.
Object 클래스의 equals() 메소드는 비교 연산자인 ==과 동일한 결과를 리턴한다.
두 객체가 동일한 객체라면 true를 리턴하고 그렇지 않으면 false를 리턴한다.
Object obj1 = new Obejct();
Obejct obj2 = new Object();
// 결과 동일
// obj1(기준 객체), obj2(비교 객체)
boolean result = obj1.equals(obj2);
boolean result = (obj1 == obj2)
자바에서는 두 객체를 동등 비교할 때 equals() 메소드를 흔히 사용한다.
equals() 메소드는 두 객체를 비교해서 논리적으로 동등하면 true를 리턴하고, 그렇지 않으면 false를 리턴한다. 논리적으로 동등하다는 것
은 같은 객체이건 다른 객체이건 상관없이 객체가 저장하고 있는 데이터가 동일함을 뜻한다.
예를 들어 String 객체의 equals() 메소드는 String객체의 번지를 비교하는 것이 아니고, 문자열이 동일한지 조사해서 같다면 ture, 그렇지 않다면 false를 리턴한다.
이것이 가능한 이유는 String 클래스가 Object의 equals()메소드를 재정의(오버라이딩)해서 번지 비교가 아닌 문자열 비교로 변경했기 때문이다.
Object의 equals() 메소드는 직접 사용되지 않고 하위 클래스에서 재정의하여 논리적으로 동등 비교할 때 이용한다.
예를 들어 Member 객체는 다르지만 id 필드값이 같으면 논리적으로 동등한 객체로 취급하고 싶을 경우 Object의 equals() 메소드를 재정의하여 id 필드값이 같음을 비교하면 된다.
Member 클래스에서 equals()메소드를 재정의 한다.
public class Memeber {
public String id;
public Memeber(String id) {
this.id = id;
}
@Override
public boolean equals(Obejct obj) {
// 매개값이 Member 타입인지 확인
if(obj instanceof Member) {
//Member 타입으로 강제 타입 변환하고 id필드값이 동일한지 검사한 후, 동일하다면 true 리턴
Memeber member = (Member) obj;
if(id.equals(memeber.id)) {
return true;
}
}
// 매개값이 Memeber 타입이 아니거나 id 필드값이 다른 경우 false 리턴
return false;
}
}
public class MemeberExample {
public static void main(String[] args) {
Member obj1 = new Member("blue");
Member obj2 = new Member("blue");
Member obj3 = new Member("red");
if(obj1.equals(obj2)) {
System.out.println("obj1과 ojb2 동등하다");
} else {
System.out.println("obj1과 ojb2 동등하지 않다");
}
if(obj1.equals(obj3)) {
System.out.println("obj1과 ojb3 동등하다");
} else {
System.out.println("obj1과 ojb3 동등하지 않다");
}
}
}
첫번째는 매개값이 Member 타입이고 id 필드값도 동일하므로 true로 나오고, 두번째는 매개값이 Member 타입이지만 id필드값이 다르므로 false로 나온다.
객체 해시코드란 객체를 식별할 하나의 정수값을 말한다.
Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체마다 다른 값을 가지고 있다.
논리적 동등 비교 시 hashCode()를 오버라이딩할 필요성이 있다.
그림을 보면 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다.
해시코드 값이 다르면 다른 객체로 판단하고, 해시코드 값이 같으면 equals() 메소드로 다시 비교한다. 그렇게 때문에 hashCode()메소드가 true가 나와도 equals()의 리턴값이 다르면 다른 객체가 된다.
public class key {
public int number;
public Key(int number) {
this.number = number;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Key) {
Key compareKey = (Key) obj;
if(this.number == compareKey.number) {
return true;
}
}
return false;
}
}
Key 클래스는 equals() 메소드를 재정의해서 number 필드값이 같으면 true를 리턴하도록 했다. 그러나 hashCode() 메소드는 재정의하지 않았기 떄문에 Object의 hashCode() 메소드가 사용된다.
public class KeyExample {
public static void main(String[] args) {
//Key 객체를 식별키로 사용해서 String 값을 저장하는 HashMap 객체 생성
HashMap<Key, String> hashMap = new HashMap<Key, String>();
//식별키 "new Key(1)"로 강아지 저장
hashMap.put(new Key(1), "강아지");
//식별키 "new Key(1)"로 강아지 읽음
String value = hashMap.get(new Key(1));
System.out.println(value);
}
}
이런 경우 HashMap의 식별키로 Key 객체를 사용하면 저장된 값을 찾아오지 못한다.
왜냐하면 number 필드값이 같더라도 hashCode() 메소드에서 리턴하는 해시코드가 다르기 때문에 다른 식별키로 인식하기 때문이다. value의 결과는 null이 나온다.
public class key {
...
@Override
public int hashCode() {
return number;
}
}
강아지를 읽으려면 hashCode() 메소드 재정의를 추가 해 준다.
hashCode()의 리턴값을 number 필드값으로 했기 때문에 저장할 때의 "new Key(1)"와 읽을 때의 "new Key(1)"은 같은 해시코드가 리턴된다.
저장할 때의 "new Key(1)"와 읽을 때의 "new Key(1)"은 사실 서로 다른 객체이지만 HashMap은 hashCode()의 리턴값이 같고, equals() 리턴값이 true가 나오기 때문에 동등 객체로 평가한다. 즉, 같은 식별키로 인식한다는 뜻이다.
💚 결론적으로 객체의 동등 비교를 위해서 Object의 equals() 메소드만 재정의하지 말고 hashCode() 메소드도 재정의해서 논리적 동등 객체일 경우 동일한 해시코드가 리턴되도록 해야 한다.