많은 사람들도 그렇고 나도 아래의 기본 개념으로 이해하고 있었다.
equals() : 두 객체의 "내용"이 같은지 비교한다.
hashcode() : 객체의 고유한 해시코드를 반환한다.
그러면 2개의 값이 동일한 객체를 만들어서 equals()를 사용하면 true가 나와야겠네?
Human h1 = new Human("박기현", 23);
Human h2 = new Human("박기현", 23);
System.out.println(h1.equals(h2)); // -> 결과 : false
그러나 실제 결과는 false가 나온다.
그 이유는 Object 타입의 equals()메서드 내부를 보면 이해할 수 있다.
내부에서 ==을 이용해서 값을 리턴한다.
==을 사용하게 되면 두 객체의 주소값이 같은지 확인한다. 현재 인스턴스를 2개 만들었으니, heap 영역에 2개의 객체가 생성되었고 각 주소값은 다르게 된다. 그래서 false가 나오는 것.
같은 값이니 true가 나오게 만들려면 equals()를 오버라이드 해서 재정의를 해줘야 한다.
아래의 규칙을 따라 equals()를 재정의해주면 된다.
아래는 Human 클래스에 equals()를 재정의한 방법이다.
@Override
public boolean equals(Object o){
if(o == this) return true;
if(o instanceof Human){
Human human = (Human) o;
if(human.getAge() == getAge() && human.getName().equals(getName())){
return true;
}
}
return false;
}
이제 해당 코드가 어떤 내용을 가지는지 하나씩 살펴보자.
if(o == this) return true;
파라미터로 받은 객체와 현재 객체의 주소값이 같으면 heap 영역에 같은 인스턴스를 가리키고 있다는 소리다.
그러므로 바로 true를 리턴해주면 된다.
if(o instanceof Human){
Human human = (Human) o;
if(human.getAge() == getAge() && human.getName().equals(getName())){
return true;
}
}
받은 객체가 Human 타입이거나 하위 타입인지 판단한다. 만약에 엉뚱한 타입의 객체가 들어오면 바로 false를 리턴한다.
o를 다형성을 통해 Human으로 변환시켜주고, 기존 값과 비교 값의 필드 값을 전부 비교해서 일치하면 true를 리턴한다.
age는 기본타입이라 ==으로 값 비교, name은 참조타입이라 String의 equals()로 비교를 진행.
이렇게 하면 같은 값을 가지는 객체일 경우 true를 반환해주게 된다.
그런데 equals()를 재정의하면 꼭 하는 말이 있다.
'hashcode()도 재정의를 진행해라'
우선 hashcode는 각 인스턴스의 고유한 값이다.
어 그러면 주소값이랑 뭐가 다른건데? 라는 의문을 가질 수 있다.
주소값 : 객체가 저장된 메모리의 위치를 알려주는 고유한 식별자.
해시코드 : 객체의 내용을 바탕으로 파생된 정수 값. 즉 메모리 주소를 기반으로도 만들어지고, 객체의 상태나 값에 의해서 고유한 값을 만들어낸다.
결국 두 객체가 같다고 equals()를 재정의 하게 되면, hashcode()도 재정의 해서 같은 hashcode를 가지게 해야한다.만약에 hashcode를 재정의 하지 않으면 hash를 사용하는 컬렉션에서 문제가 발생한다.
아래의 예시를 들 수 있다.
Map<Human, Integer> map = new HashMap<>();
map.put(new Human("박기현", 23), 1);
System.out.println(map.get(new Human("박기현", 23)));
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
null 출력됨.
해당 코드를 보면 HashMap에 Human을 생성해서 넣고 출력에서 꺼내도록 만들었다.
같은 값을 가지는 객체이기에 value인 1이 나와야 하는데 결과는 null이 나오게 된다.
그 이유는 hashcode를 재정의하지 않아서 서로 다른 hashcode를 반환하기에 Map에서 키 값을 찾지 못하게 되는 것이다.
@Override
public int hashCode(){
final int PRIME = 1;
int result = 1;
result += PRIME * result + getAge();
return result;
}
그래서 위 코드처럼 hashCode()를 재정의 해주었다.
getAge()를 사용해서 해당 객체의 필드 값에 의해서 hashcode가 만들어지게 된다.
이렇게 재정의를 하면 동일한 값이 들어왔을 때 동일한 해시코드를 내뱉어주게 된다.
재정의를 통해 Map의 value값을 제대로 찾아오는 걸 확인할 수 있다.
정리
객체의 동등성을 판단해야 하는 경우 equals() 재정의를 진행해준다.
equals()를 재정의 하는 경우 hashCode() 재정의도 함께 진행해서 hash를 사용하는 컬렉션의 문제를 방지해야한다.
'JAVA' 카테고리의 다른 글
ArrayList는 어떻게 데이터를 관리할까?(디버깅 과정 포함) (0) | 2024.07.02 |
---|---|
자바 실행 과정 및 JVM 개념 정리 (1) | 2024.03.01 |
Class에 @Builder 붙이면 안되는 이유가 무엇일까? (0) | 2024.02.07 |
자바 Record (자바 16에서 채택된 새로운 클래스) (0) | 2024.01.10 |