[F-Lab 모각코 챌린지 6일차] equals(), hashCode()
TIL
- equals()
- hashCode()
equals()
/**
* Object 클래스에서의 equals()
*/
public boolean equals(Object obj) {
return (this == obj); // 객체 주소값 비교
}
Object
클래스의 equals()
메소드는 객체 자신(this)의 주소값과 매개변수 객체의 주소값을 비교해서 같으면 true, 다르면 false를 반환한다.
/**
* String 클래스에서 오버라이딩한 equals()
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String
클래스에서 오버라이딩한 equals()
메소드는 문자값 그 자체를 비교한다.
/**
* 사용자 정의 클래스에서 오버라이딩한 equals()
*/
public class Person {
String name;
// Person 객체의 name 필드가 동등한지 비교하기위해 오버라이딩
public boolean equals(Object o) {
// this와 매개변수 객체 주소값이 같을 경우 true
if (this == o) return true;
// 매개변수 객체가 Person 타입과 호환되지 않으면 false
if (!(o instanceof Person)) return false;
// 매개변수 객체를 다운캐스팅 (name 변수가 Object에는 없고 Person에는 있으니까)
Person person = (Person) o;
// this의 name 필드와 매개변수 객체의 name 필드를 비교
return Objects.equals(this.name, person.name);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("홍길동");
Person p2 = new Person("홍길동");
System.out.println(p1.equals(p2)); // true
}
}
그렇다면 내가 만든 Person
클래스로 객체를 생성하고 그 객체들을 비교할 땐, 어떻게 해야할까?
컴퓨터 입장에서 보면 p1
, p2
는 heap 영역에 전혀 다른 주소값을 갖고 있는, 서로 다른 객체라고 보겠지만
현실 세계 관점에서 보면 두 객체는 name
속성이 같은 데이터라고 볼 수도 있다.
이렇게 객체의 인스턴스 변수값으로 객체를 비교 하고싶다면 equals()
메소드를 오버라이딩하면 된다.
그런데 한 가지 유념해야할 것이 있다.
equals
메소드를 오버라이딩할 때는 hashCode()
메소드도 함께 오버라이딩 해야한다. 위 코드처럼 equals()
메소드를 오버라이딩 해서 name
필드가 '홍길동'인 객체는 서로 같다고 판단할 수 있겠지만 서로 가리키고 있는 객체의 주소값은 완전 다르다. equals()
결과가 true임에도 불구하고 hashCode()
의 값은 서로 다른 것이다. 이렇게 되면 Java Collection Framework 사용 시, 문제가 발생한다.
따라서 equals()
메소드의 결과가 true인 두 객체는 해시코드도 반드시 같아야 한다.
hashCode()
public native int hashCode();
hashCode()
메소드는 객체에 대한 해시코드 값을 리턴한다. 해시코드란 객체의 주소값으로 만든 고유한 숫자값을 말한다. 해싱 알고리즘을 통해 객체의 주소를 int로 변환해서 반환한다.
위 코드에서 선언 된 native
라는 키워드는 native method라는 뜻이다. 즉, OS가 가지고 있는 메소드라는 뜻이다. 주로 C언어로 작성된 경우가 많다. 코드를 보면 메소드 구현부가 없는데 이미 작성 된 메소드를 호출하는 것이라 내용이 없는 것이다. 또 우리는 그 안에 작성된 코드를 볼 수 없다.
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("홍길동");
Person p2 = new Person("홍길동");
// 객체마다 서로 다른 해시코드를 가지고 있다.
System.out.println(p1.hashCode()); // 622488023
System.out.println(p2.hashCode()); // 1933863327
}
}
해시코드는 객체의 주소를 가지고 만들기 때문에 객체마다 서로 다른 값을 갖는다. 그래서 해시코드를 객체의 지문이라고도 표현한다. 앞에서 equals()
메소드를 오버라이딩 하면 hashCode()
메소드도 오버라이딩 해야한다고 했다.
그 이유를 더 자세히 알아보면,
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person("홍길동");
Person p2 = new Person("홍길동");
// 해시코드 서로 다름
System.out.println(p1.hashCode()); // 460141958
System.out.println(p2.hashCode()); // 1163157884
// equals() 메소드를 오버라이딩 했다고 가정함
// equas() 값은 true이고, 해시코드 값은 서로 다름
System.out.println(p1.equals(p2)); // true
Set<Person> people = new HashSet<>();
people.add(p1);
people.add(p2);
System.out.println(cars.size()); // 2
}
}
p1.equals(p2)
결과가 true이기 때문에 중복 데이터를 허용하지않는 Set에 두 객체를 넣었을 때, 한 개의 데이터만 저장될 것이라고 예상했다. 그런데 people
의 size()
결과값이 2이다. 이는 두 객체를 equals()
메소드를 통해 논리적으로 같다고 정의했지만 해시코드가 다르기 때문에 발생한 문제이다.
hashCode()와 equals() 동작순서
hash 값을 사용하는 Collection(HashMap, HashSet, HashTable)은 객체가 논리적으로 같은지 비교할 때, 이와 같은 과정을 거친다.
- 컬렉션에 데이터가 추가되면 해당 데이터의
hashCode()
리턴값을 해당 컬렉션에서 가지고 있는지 찾아본다. - 해시코드가 같으면 다음으로
equals()
리턴값을 비교한다. equals()
가 true를 리턴하면 논리적으로 서로 같은 객체라고 판단한다.
위에 있는 코드와 비교해보면 p1
객체와 p2
객체는 hashCode()
리턴값이 다르기 때문에 첫 단계에서부터 서로 다른 객체라고 판단해버린다.
따라서 이런 오류를 막기위해 hashCode()
메소드도 오버라이딩해서 서로 같은 해시코드를 갖도록 만들어야 한다.
class Person {
String name;
public Person(String name) {
this.name = name;
}
/**
* 해시코드 재정의
*/
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals() {
// 오버라이딩 했다고 가정
}
}
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person("홍길동");
Person p2 = new Person("홍길동");
// 해시코드가 서로 같다.
System.out.println(p1.hashCode()); // 54150093
System.out.println(p2.hashCode()); // 54150093
System.out.println(p1.equals(p2)); // true
Set<Person> people = new HashSet<>();
people.add(p1);
people.add(p2);
System.out.println(cars.size()); // 1
}
}
Person
클래스의 name
필드를 가지고 해시코드를 생성하도록 hashCode()
메소드를 오버라이딩 한다. 이제 두 객체의 해시코드가 같은 것을 볼 수 있다. Set 컬렉션도 p1
, p2
객체를 중복된 데이터로 판단해서 한 개의 데이터만 저장한다.
📚 참고
자바의 신
자바 equals / hashCode 오버라이딩 - 완벽 이해하기
[자바의 정석 - 기초편] ch9-1~3 Object클래스와 equals()
[자바의 정석 - 기초편] ch9-4~6 hashCode(), toString()
'JAVA' 카테고리의 다른 글
[F-Lab 모각코 챌린지 8일차] shift operator (0) | 2023.06.15 |
---|---|
[F-Lab 모각코 챌린지 7일차] Generics (1) | 2023.06.14 |
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션 (0) | 2023.06.12 |
[F-Lab 모각코 챌린지 4일차] Garbage Collector (0) | 2023.06.11 |
JVM 메모리 구조 (2) | 2023.06.10 |
댓글
이 글 공유하기
다른 글
-
[F-Lab 모각코 챌린지 8일차] shift operator
[F-Lab 모각코 챌린지 8일차] shift operator
2023.06.15 -
[F-Lab 모각코 챌린지 7일차] Generics
[F-Lab 모각코 챌린지 7일차] Generics
2023.06.14 -
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션
2023.06.12 -
[F-Lab 모각코 챌린지 4일차] Garbage Collector
[F-Lab 모각코 챌린지 4일차] Garbage Collector
2023.06.11