Equals()와 Hashcode()를 override 해야하는 이유

이호인·2022년 4월 13일
1

Java

목록 보기
3/6

📌 Equals & HashCode

  • equals와 hashCode는 모든 Java 객체의 부모인 Object 클래스에 정의되어 있다. 그렇기 때문에 모든 객체는 Object 클래스에 정의된 equals와 hashCode 함수를 상속받고 있다.

📌 equals()

  • 2개의 객체가 동일한지 검사하기 위해 사용된다.
  • 2개의 객체가 참조하는 것이 동일한지 확인.
  • 2개의 객체가 가리키는 곳이 동일한 메모리 주소일 경우에만 동일한 객체!
public boolean equals(Object obj){
	return (this == obj);
}

🔍 여기서...
String을 비교할 때 equals로 비교해야 하는 이유
라는 주제로 정리한 적이 있었다.

== 연산자equals의 차이를 정리해두었는데, String 클래스equals 함수를 재정의하여 메모리 주소 비교가 아닌 문자열 '값'을 비교하기 때문에 equals를 활용해서 비교할 수 있었던 것이었다.

  • 동일성 비교 == , 객체 인스턴스의 주소 값을 비교
    (primitive data type의 경우 ==를 통해 비교가 가능)
  • 동등성 비교 equals() 메소드를 사용, 객체 내부의 값을 비교

📌 hashCode

  • int hashCode()로 정의된 hashCode()는 실행 중(Runtime) 객채의 유일한 integer값을 반환한다.
  • Object 클래스에서는 heap에 저장된 객체의 메모리 주소를 반환하도록 되어있다. (항상 그렇지는 않음)
public native int hashCode();

native 키워드는 메소드가 JNI(Java Native Interface)라는 native code를 이용해 구현되었음을 의미한다.
native는 메소드에만 적용 가능한 제어자이며, Java가 아닌 C or C++ 등 언어로 구현된 부분을 JNI를 통해 Java에서 이용하고자 할 때 사용된다. (일반 개발자들은 사용할 수 없다.)
hashCode는 HashTable과 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용된다.

Person person1 = new Person("hoin");
Person person2 = new Person(new String("hoin"));
Person person3 = person2;

// person2,3의 주소는 같지만, person 1의 주소는 다르다. 따라서 해시코드 값도 person1만 다르다.
System.out.println(person1.hashCode()); 
System.out.println(person2.hashCode());
System.out.println(person3.hashCode());

Class Person {
	String name;
    
    Person(String name) {
    	this.name = name;
    }
    
    @Override
    public boolean equals(Object obj) {
    	Person anotherObj = (Person) obj;
        return (this.name.equals(anotherObj.name));
    }
}

🔍 equals도 봤으니까... (궁금해짐)
String 클래스에서 equals()를 재정의한 것 처럼 hashCode()를 재정의한 것을 살펴보자.

문자열에서 한 글자씩 가져와서 정수값으로 변경한다. 한 글자를 가져와서 정수와 더하면 해당 글자의 ascii 코드의 값을 사용한다.

  • 31을 곱하는 이유는 홀수이기 때문.
  • 짝수를 곱했을 때 오버플로우 되면 정보 손실이 발생할 수 있기 때문.
  • 이진수에서 2를 곱하면 비트가 왼쪽으로 한 비트씩 이동하기 때문.
  • 홀수 중 특별히 31의 장점 : 31 * i(i<<5) - i 와 같기 때문에 곱셈 대신 비트 이동 및 뺄셈으로 처리하여 성능면에서 좀 더 이득이 있기 때문.
  • 그러나 요즘 VM은 자동으로 최적화.

결론 : 주소값 기준으로 정수값의 hashCode를 생성하지 않음. 서로 다른 String 객체도 문자열이 같으면 hashCode가 같다. hashCode가 유일한 값이라고 했지만 String에서 중복 가능성이 있는 이유.


📌 equals와 hashCode의 Override

  1. equals() Override 필요성 (Integer age을 필드로 가지는 Person 클래스가 있다고 가정)
Person person1 = new Person();
Person person2 = new Person();
// new를 사용해서 객체 생성을 했으므로 서로 주소가 다를 것이다.

person1.setAge(29);
person2.setAge(29);

System.out.println(person1.equals.(person2)); // 주소값이 다르기 때문에 false
  • Person에 equals 메소드를 Override 해주어야한다.
public boolean equals(Object o) {
	if(o == null) {
    	return false;
    }
    if (o == this) {
    	return true;
    }
    if (getClass() != o.getClass()) {
    	return false;
    }
    
    Person p = (Person) o;
    return (this.getAge() == p.getAge());
}
  • 하지만 아직 문제가 남아있다. hashCode()는 해당 메모리 주소값을 반환한다고 했다. 그렇기 때문에 주소값이 다른 person 1과 person2는 다른 해시값을 반환할 것이고 HashSet에 저장하려하면 2개의 객체가 서로 다른 위치에 저장될 것이다.

  • hashCode 메소드도 함께 오버라이드하여 수정해주어야 한다.

@Override
public int hashCode() {
	final int PRIME = 31; // 왜 31인지는 위에서 설명했다!
    int result = 1;
    result = PRIME * result + getAge();
    return result;
}
  • 이 작업까지 모두 거치면 hashSet에도 1개의 데이터만 저장되게 된다.

마치며

  • equals와 ==를 정리한 글이 생각나면서 그땐 잘 이해하지 못하고 정리했구나라는 반성을 했다.
  • String은 특별한 클래스라는 것을 다시 한번 알게 되었음.
  • VO에 equals와 hashCode를 사용한다고 본 것 같은데 나중에 DTO와 VO 정리도 해봐야겠다.

reference!

https://brunch.co.kr/@mystoryg/133
https://jisooo.tistory.com/entry/java-hashcode
https://mangkyu.tistory.com/101

profile
공부 기록

0개의 댓글