공부하는 스누피

[Java] equals()와 hashCode() 본문

Languages/Java

[Java] equals()와 hashCode()

커피맛스누피 2021. 2. 27. 00:05

equals 메서드는 Object 클래스에 정의되었지만, 객체 인스턴스 간 논리적 동치를 확인하기 위해 종종 재정의된다. 이 메서드를 재정의한다면 반드시 hashCode 메서드도 재정의해야 한다. hash를 사용하지 않는 클래스라도 Hash 기반의 자료구조의 요소일 경우 hashCode를 사용하게 되기 때문이다.

 

Object 클래스에서는 equals 메서드를 단순 주소값 비교로만 사용한다. ==와 같은 역할을 한다고 보면 된다. Object를 상속받은 String같은 클래스의 경우 부모 클래스의 equals 메서드를 그대로 쓸 수는 없다. "hi"와 "hi"가 논리적으로는 같은 문자열인데 다른 String 인스턴스 변수에 들어 있으면 주소값을 비교해 다른 객체라는 결과가 나와버린다. 그래서 String뿐만 아니라 다른 여러 클래스들은 필요에 따라 equals 메서드를 재정의한다. 이때 equals 메서드에서 객체가 같은지 비교하는 척도가 되는 필드를 '핵심' 필드라고 한다.

 

equals 메서드 구현 방법

@Override
public boolean equals(Object o){
    if(o == this)
    	return true;
    if(!(o instanceof MyClass))
    	return false;
    MyClass obj = (MyClass)o; 
    return obj.field1 == this.field1 && obj.field2 == this.field2 && ... ;
}

1. == 연산자를 사용해 입력이 올바른(자기 자신의) 참조인지 확인한다.

2. instanceof 연산자로 파라미터로 받은 객체가 올바른 타입인지 확인한다.

3. 올바른 타입으로 형변환한다.

4. 핵심 필드들이 모두 일치하는지 확인한다.

 

 

hashCode 메서드 또한 Object 클래스에서 구현되었는데, 객체의 hash code 값을 반환한다. docs에 따르면 hashCode 메서드는 HashMap같은 hash table의 장점을 지원한다. 규약 중에는 equals 메서드가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 같은 값을 반환해야 한다고 되어 있다. hash table은 hash값을 기준으로 값을 배치하는데 key가 되는 객체의 논리적 동치와 물리적 동치(주소값)이 일치하지 않는다면 hash의 장점을 떨어뜨린다. 다음 예시를 참고하면 될 것 같다.

 


정수 x, y를 멤버 변수로 갖고 있는 Point라는 클래스가 있다고 하자.

여러 개의 이차원 배열에서 특정 값의 위치와 해당 위치에서 나타난 빈도수를 저장하는 프로그램을 짠다고 하면 Point 클래스를 값의 위치를 나타내는 데 쓸 수 있다. 그러면 Point를 key로 하고, 특정 값이 key가 되는 Point에서 나타난 횟수를 value로 저장하는 HashMap으로 만들면 된다. Point 객체의 구별을 위해 equals 메서드는 멤버 변수 x와 y가 모두 같아야 true를 반환하게 했다.

 

이제 3개의 이차원 배열과 값 10이 주어졌을 때 순서대로 어디에 10이 있는지 확인하고 위치를 Point 객체로 만들어 Map에 넣어 보자.

첫 번째 이차원 배열에서는 (1, 2)에 10이 있다. 그러면 x=1, y=2로 하는 Point 객체를 만들고, 이를 HashMap의 key로 해서 넣으면 된다.

for(int i=0;i<arr.length;i++){
    for(int j=0;j<arr[0].length;j++){
        if(arr[i][j] == 10){
            Point p = new Point(i, j);
            table.put(p, 1); // hashMap에 넣는다.
        }
    }
}

두 번째 이차원 배열에서는 (2, 1)에 10이 있다. 그러면 x=2, y=1로 하는 Point 객체를 만들어 넣으면 된다.

세 번째 배열에는 (1, 2) 위치에 10이 있다. 첫 번째에서 한번 나타났기 때문에 HashMap의 (1, 2)위치의 카운트를 올려주기만 하면 된다.

if(table.containsKey(p)){
	table.update(p, table.get(p)+1);
}

그런데 여기서 Point 클래스에 hashCode를 수정하지 않았다면 x, y값이 같은 객체라도 다른 객체로 판별된다. 카운트 값이 올라가지 않게 되는 것이다.

 

hashCode를 다음과 같이 수정하면 논리적 동치의 기준으로 사용되는 값이 hash값의 기준이 되어 x, y값의 같은 객체라면 같은 hash값을 가지게 해 준다. new Point(1, 2) == new Point(1, 2)가 성립된다.

@Override
public int hashCode(){
	int result = 0;
    result = (31*result) + Integer.hashcode(x);
    result = (31*result) + Integer.hashcode(y);
    
    return result;
}

 


hashCode 메서드 작성 요령

1. int 변수 result를 선언하고 equals 비교에 사용되는 값들을 해시화한 c로 초기화시킨다.

2. equals 비교에 사용되는 값(핵심 필드) 각각에 대해 다음 연산을 수행한다.

2-a. 해당 필드의 해시코드를 계산한다. Primitive type일 경우 Type.hashCode(f)를 수행하고, 아니면 표준형의 hashCode를 호출해도 된다.

2-b. 기존 result값에 31을 곱하고 2-a에서 구한 코드를 더한 값을 result에 저장한다.

3. result를 반환한다.

 

 

 

(참고)

Effective Java 3/E (2018). Joshua B(이복연 옮김). 프로그래밍 인사이트

Comments