equals를오버라이딩할 때hashCode도 같이오버라이딩해야 되는이유에 대해 정리한 글.
1번이 이해가 잘 되지 않는다면 2, 3, 4내용을 한번 훑어보고 보는 걸 추천함.동일한 두 객체는 동일한 메모리 주소를 가짐.
동일한 객체는 동일한 해시코드를 가져야함.equals() 메서드를 오버라이딩해야 한다면 hashCode() 메서드도 같이 오버라이딩 해야함.두 객체가 equals()메서드의 결과값이 동일하다면, 두 객체의 hashCode() 값도 동일해야하고,
두 객체가 equals()메서드의 결과값이 동일하지 않다면, 두 객체의 hashCode() 값은 동일하지 않아도 됨.
obj1.equals(obj2) == True --> hashCode(obj1) == hashCode(obj2)이여야함.필요는 없음.String 클래스는 Object로부터 상속 받은 hashCode()를 오버라이딩해서 문자열 내용으로 해쉬코드를 만듦.
서로 다른 String 인스턴스라도 같은 내용의 문자열을 가지고있다면 hashCode()를 호출하면 같은 해쉬코드를 얻음.서로 다른 두 객체에 대해 equals()의 결과값이 true이면서 hashCode()의 반환값이 같아야 같은 객체로 인식함.
equals()를 오버라이딩을 통해 재정의 한다면 hashCode()도 같이 재정의해서 equals()의 결과가 true인 두 객체의 해쉬코드(hashCode())의 결과값이 항상 같도록 해줘야 됨.equals()의 호출결과가 true이지만 해쉬코드가 다른 두 객체를 서로 다른 것으로 인식하고 따로 저장함.equals()만 재정의(오버라이딩)
public class tmp {
public static void main(String[] args) {
HashMap<PersonTest, String> map = new HashMap<>();
PersonTest p1 = new PersonTest("Hajju");
PersonTest p2 = new PersonTest("Hajju");
System.out.println("p1.equals(p2) : " + p1.equals(p2));
map.put(p1, "Java");
map.put(p2, "Spring"); // 동일한 name이지만 다른 객체로 인식
System.out.println("map.size() : " + map.size()); // 결과: 2 (잘못된 결과)
}
}
class PersonTest {
String name;
PersonTest(String name) {
this.name = name;
}
// equals()만 재정의
@Override
public boolean equals(Object o) {
if (o instanceof PersonTest) {
PersonTest p = (PersonTest) o;
return this.name.equals(p.name);
}
return false;
}
// @Override
// public int hashCode() {
// return name.hashCode(); // name이 같으면 같은 해시값 반환
// }
}
-- 실행 결과 --
p1.equals(p2) : true
map.size() : 2

equals(), hashCode() 둘 다 재정의(오버라이딩)
public class tmp {
public static void main(String[] args) {
HashMap<PersonTest, String> map = new HashMap<>();
PersonTest p1 = new PersonTest("Hajju");
PersonTest p2 = new PersonTest("Hajju");
System.out.println("p1.equals(p2) : " + p1.equals(p2));
map.put(p1, "Java");
map.put(p2, "Spring"); // 동일한 name이지만 다른 객체로 인식
System.out.println("map.size() : " + map.size()); // 결과: 2 (잘못된 결과)
}
}
class PersonTest {
String name;
PersonTest(String name) {
this.name = name;
}
// equals() 재정의
@Override
public boolean equals(Object o) {
if (o instanceof PersonTest) {
PersonTest p = (PersonTest) o;
return this.name.equals(p.name);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode(); // name이 같으면 같은 해시값 반환
}
}
-- 실행 결과 --
p1.equals(p2) : true
map.size() : 1

동등하다는 의미.같은 값을 갖고 있는 경우를 의미함.동등성은 변수가 참조하고 있는 객체의 주소가 서로 달라도 안에 저장되어 있는 값이 같으면 두 변수는 동등함.equals() 메서드는 동등성(equality)을 비교함.equals메서드는 Object에 정의되어 있는 것이 아닌, 오버라이딩 된 것임.Object 클래스에 정의된 기본 equals() 메서드는 == 연산자와 동일하게 주소값을 비교함.String, File, Date, wrapper(Integer, Double 등)내용을 비교하도록 오버라이딩 되어 있음.String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1.equals(str2));
System.out.println(str1 == str2);
-- 실행 결과 --
true (내용 비교)
false (다른 메모리 주소)
equals()는 문자열의 값을 비교하여 true를 반환.==는 두 객체가 동일한 메모리 위치에 있는지 비교함. 다른 객체를 참조하므로 false를 반환.Object에 정의되어 있는 equals()는 객체 비교, 즉 객체의 주소(참조 변수값)를 비교함.동일성 비교.public boolean equals(Object obj) {
return (this == obj); // 기본 구현
}
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;
}
JDK 21
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
같은 메모리 주소를 참조하는 경우를 의미.== 연산자를 사용.Number number1 = new Number();
Number number2 = number1;
System.out.println(number1 == number2);
-- 실행 결과 --
true
따라서 ==연산자는 동일성(identity)을 비교함.
기본 타입(Primitive Types)
참조 타입(Reference Types)
메모리 주소가 같으면 동일.== 연산자.== 연산자는 객체의 동일성을 판별하기 위해 사용.값이 같으면 동등.equals().==연산자equals()