객체의 동등성과 동일성에 대해서 정리할 필요가 있다.
동일성 : 같은 주소값을 가짐
동등성 : 객체 내부에서 값을 비교 (hashCode와 equals를 재정의하여 비교)
우리가 == 과 equals()의 차이점을 String Type의 변수에서 확인했듯이 ==은 동일성을 판단하는 연산자였고 equals()는 동등성을 판단하는 메서드였다.
equals()
public boolean equals(Object obj) {
return (this == obj);
}
위 equals 메서드는 Object 클래스 내부에서 선언된 equals이다. this와 파라미터로 받은 obj의 주소값을 비교하고 그 결과 논리값을 반환한다. 보통 새로운 클래스를 만들게 되면 이 equals()를 재정의하여 객체 사이의 동등성을 비교할 수 있도록 새로운 형태의 equals() 로직을 만들어야 한다.
보통은 아래와 같은 순서를 지키도록 재정의하고 조건들을 지나가며 논리값을 반환한다.
순서 1. 두 객체가 동일한 참조 값을 가지는지 확인합니다. 둘 다 동일한 참조 값을 가지는 경우 데이터도 동일하므로 true를 반환하도록 합니다.
순서 2. 매개변수로 전달받은 객체가 null이거나 두 객체가 동일한 타입이 아니라면, false를 반환하도록 합니다.
순서 3. 매개변수로 전달받은 매개변수를 현재 클래스 타입으로 변환합니다.
순서 4. 모든 필드의 값을 비교합니다. 기본 타입이라면 == 연산자를 사용하고 참조 타입인 경우 Objects 클래스의 equals() 메서드를 사용할 수 있습니다.
class Lecture {
String name;
String professor;
Lecture (String name, String professor) {
this.name =name;
this.professor= professor;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Lecture other = (Lecture) obj;
return name.equals(other.name) && professor.equals(other.professor);
}
}
public class Main {
public static void main(String[] args) throws Exception {
Lecture l1 = new Lecture("자료구조", "박현성");
Lecture l2 = new Lecture("자료구조", "박현성");
System.out.println("l1.equals(l2) = " + l1.equals(l2));
System.out.println("l1.hashCode() = " + l1.hashCode());
System.out.println("l2.hashCode() = " + l2.hashCode());
}
}
/**
* l1.equals(l2) = true
* l1.hashCode() = 705927765
* l2.hashCode() = 366712642
*/
이제 동등성을 비교하기 위해 값이 같은 객체는 eqauls()가 true를 반환하도록 해주었지만 문제가 하나 더 남아있다. 바로 hashCode이다.
hashCode()
public native int hashCode();
위는 Object 클래스에 정의되어 있는 hashCode 메서드이다. native 키워드는 보통 C나 C++과 같이 자바가 아닌 언어로 구현한 후 자바에서 사용하려고 할 때 이용하는 키워드니 참고 바란다.
위 메서드는 객체가 가지는 객체를 식별하는 하나의 고유 정수값인 hashcode를 반환한다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 hashcode를 만들어 리턴하기 때문에 객체마다 고유의 다른 값을 가져야 한다.
/**
* l1.equals(l2) = true
* l1.hashCode() = 705927765
* l2.hashCode() = 366712642
*/
따라서 같은 값을 가지는 객체가 동일성을 지니게 만들고 싶다면 두 객체는 같은 hashcode를 가질 수 있도록 hashCode() 메서드를 재정의 해주어야 한다.
@Override
public int hashCode() {
return Objects.hash(name, professor);
}
/**
* l1.equals(l2) = true
* l1.hashCode() = 1389809412
* l2.hashCode() = 1389809412
*/
여기까지 보통 클래스를 새로 만들게 될 때 equals()와 hashCode()를 꼭 재정의해서 사용하는 과정을 보여주었다.
equals()와 hashCode()를 같이 재정의해야 하는 이유
equals()만 재정의하지 않으면 hashcode()가 만든 해시값을 이용해 객체가 저장된 버킷을 찾을 수 있지만 객체가 자신과 같은 값을 가지고 있는지 확인할 수 없기 때문에 원하는 객체를 찾을 수 없다.
hashCode()만 재정의하지 않으면 같은 값이라도 hashcode값이 달라지게 된다. hash 값을 사용하는 Collection(HashMap, HashSet, HashTable)은 객체가 논리적으로 같은지 비교할 때 equals()뿐 아니라 hashCode()를 이용하여 동등비교를 하므로 필수적으로 동등성을 확인하기 위한 재정의가 필요하다..
이러한 이유로 객체의 정확한 동등, 동일 비교를 위해서는 Object의 equals()와 hashCode()는 반드시 재정의 후 사용해야 한다.
+ 추가) 다른 객체임에도 hashCode()가 같을 수 있다.
두 객체를 equals() 메서드로 비교하여 true가 나온다면 hashCode()는 일반적으로 true를 반환한다. 하지만, HashTable이나 HashSet, HashMap 등 hash를 사용하는 라이브러리 인터페이스를 사용하는 환경에서 다른 객체가 동일한 hash값을 가지는 hash 충돌이 일어난다면 다른 객체 임에도 hashCode() 값이 같을 수도 있게 된다.
'백엔드 개발자라면 대답해야 할 100가지 질문' 카테고리의 다른 글
6. throw와 throws의 차이는 무엇인가요? (0) | 2023.07.25 |
---|---|
5. 자바에서 final의 기능은 무엇입니까? (0) | 2023.07.24 |
3. equals와 ==의 차이점이 뭔지 아시나요? (0) | 2023.07.19 |
2. JRE와 JDK의 차이점 (0) | 2023.07.17 |
1. SOLID 원칙 (0) | 2023.07.15 |
댓글