[Java] hashCode() - equals() 재정의
2022. 2. 20. 21:53ㆍLanguage`/Java
hashCode() / equals()
- 둘다 Java의 Object 클래스에 정의되어 있는 메소드이다
- 따라서, Java의 모든 객체는 equals(), hashCode()를 상속받는다
hashCode()?
- 해싱기법에 사용되는 "해시함수"를 구현한 메소드이다
- 해시함수는 찾고자하는 값을 입력하면, 해당 값이 저장된 위치를 알려주는 해시코드를 return한다
- 일반적으로 해시코드가 동일한 두 객체가 존재하는 것은 가능하다
- Object클래스에 정의된 hashCode()는 객체의 주소값으로 해시코드를 생성해서 return한다
- 32bit JVM : 서로 다른 두 객체는 절대로 동일한 해시코드를 가질 수 없다
- 64bit JVM : 객체끼리 해시코드가 중복될 수 있다
>> 따라서, 클래스의 인스턴스 변수 값으로 객체의 같고 다름을 판별하려면 equals() + hashCode() 둘다 오버라이딩해야한다
- 왜냐하면, 같은 객체는 hashCode또한 같아야 하기 때문이다
equals()?
public boolean equals(Object obj){
return this == obj
}
- 2개의 참조변수가 서로 같은 객체를 참조하고 있는지 판별하는 메소드이다
- 결국, 두 참조변수의 값 :: 주소값이 같은지를 판단
- 따라서, equals()로 2개의 참조변수가 참조하는 객체의 값을 비교할 수는 없다
- equals()를 각 클래스에서 재정의하면 객체의 값을 비교할 수 있다
class ID{
private int id;
ID(int id){
this.id = id;
}
}
public class test{
public static void main(String[] args) {
ID id1 = new ID(10);
ID id2 = new ID(10);
System.out.println("id1과 id2 equals()? : " + id1.equals(id2));
System.out.println("id1의 해시코드 : " + id1.hashCode());
System.out.println("id2의 해시코드 : " + id2.hashCode());
}
}
- 나는 id1과 id2가 서로 값이 "10"으로 같기 때문에 equals()를 통한 값이 true로 나오길 기대했다
- but false?
- 왜냐하면, equals()를 재정의해주지 않으면 Object 클래스 자체의 "equals()"는 객체의 주소값을 기준으로 같은지 다른지 판별하기 때문이다
>> 따라서, 값을 비교하려면 클래스내에 equals()를 재정의해줘야 한다
class ID{
private int id;
ID(int id){
this.id = id;
}
int getID(){
return id;
}
@Override
public boolean equals(Object obj){
ID id = (ID)obj;
return this.getID() == id.getID();
}
}
public class test{
public static void main(String[] args) {
ID id1 = new ID(10);
ID id2 = new ID(10);
System.out.println("id1과 id2 equals()? : " + id1.equals(id2));
System.out.println("id1의 해시코드 : " + id1.hashCode());
System.out.println("id2의 해시코드 : " + id2.hashCode());
}
}
- 이제 내가 원하는대로 값들끼리의 비교가 완료되었다
String 클래스?
- 그러면 String 클래스도 equals()를 재정의하지 않으면 결과가 false로 나올까?
String 클래스의 hashCode
String클래스는 hashCode를 이미 재정의해놓았다
- 코드를 보게되면 hashCode를 String객체의 값을 기준으로 return하는 것을 알 수 있다
- 따라서, 서로 다른 String객체라도, 객체의 값만 동일하면 같은 hashCode를 return한다
public class test{
public static void main(String[] args) {
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println("s1의 해시코드 (hashCode()) : " + s1.hashCode());
System.out.println("s2의 해시코드 (hashCode()) : " + s2.hashCode());
System.out.println("s1의 identityHashCode (System.identityHashCode()) : " + System.identityHashCode(s1));
System.out.println("s2의 identityHashCode (System.identityHashCode()) : " + System.identityHashCode(s2));
}
}
- 보이는것처럼, s1과 s2는 서로 다른 객체임에도 불구하고 "Hello"라는 같은 값을 가진다
- hashCode()는 값을 기준으로 return하기 때문에 s1, s2가 동일한 해시코드를 return했다
- System.identityHashCode()는 객체의 주소값을 기준으로 해시코드를 생성하므로 서로 다른 값이 나왔다
- 물론 System.identityHashCode()는 실행 할 때마다 달라질 수는 있다
>> hashCode()를 재정의 하지 않으면, hashCode()와 System.identityHashCode()의 값은 당연히 동일할 것이다
class ID{
private int id;
ID(int id){
this.id = id;
}
int getID(){
return id;
}
}
public class test{
public static void main(String[] args) {
ID id1 = new ID(10);
ID id2 = new ID(10);
System.out.println("id1의 해시코드 (hashCode()) : " + id1.hashCode());
System.out.println("id1의 identityHashCode() : " + System.identityHashCode(id1));
System.out.println("id2의 해시코드 (hashCode()) : " + id2.hashCode());
System.out.println("id2의 identityHashCode() : " + System.identityHashCode(id2));
}
}
- ID클래스는 hashCode()를 재정의하지 않았기 때문에 hashCode()와 System.identityHashCode()의 값이 동일하다
String 클래스의 equals()
- Object클래스의 equals()는 객체의 주소값을 기준으로 같음과 다름을 판별한다고 하였다
- 이 때, 동일한 객체는 동일한 hashCode를 가진다는 것 또한 알고 있다
String 클래스의 hashCode는 String 객체의 값을 기준으로 return해준다
- 따라서, 두 String 객체가 동일한 값을 가진다면
- 두 String 객체의 hashCode가 동일하다는 것이고
- 이 뜻은, 결국 두 String 객체의 equals()는 "true"가 return된다는 것을 알 수 있다
public class test{
public static void main(String[] args) {
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println("s1와 s2 equals()? : " + (s1.equals(s2)));
System.out.println("s1의 hashCode() : " + s1.hashCode());
System.out.println("s2의 hashCode() : " + s2.hashCode());
System.out.println("s1의 identityHashCode (System.identityHashCode()) : " + System.identityHashCode(s1));
System.out.println("s2의 identityHashCode (System.identityHashCode()) : " + System.identityHashCode(s2));
}
}
- String 객체는 별도로 equals()를 재정의하지 않아도 값을 기준으로 equals()를 판별하기 때문에 위의 코드 결과가 "true"로 나왔다
hashCode()를 재정의하지 않으면?
- equals()를 통해서 두 객체가 다름을 판별했음에도 불구하고, 두 객체의 hashCode가 같은 값을 가질 수도 있다
- equals()를 통해서 두 객체가 같음을 판별했음에도 불구하고, 두 개게의 hashCode가 다른 값을 가질 수도 있다
>> 해시 충돌
- 특히 HashMap의 key값으로 해당 객체를 사용할 경우 심각한 문제가 발생한다
import java.util.*;
class Product{
String name;
int price;
Product(String name, int price){
this.name = name;
this.price = price;
}
}
public class test{
public static void main(String[] args) {
Map<Product, Integer> pdt = new HashMap<>();
pdt.put(new Product("연필", 200), 10);
pdt.put(new Product("샤프", 500), 8);
pdt.put(new Product("볼펜", 1000), 5);
Product p = new Product("연필", 200);
int count = pdt.get(p);
System.out.println(count);
}
}
- 이 경우 ("연필", 200)의 개수를 저장하는 count에 10이 저장될 거라고 예측이 된다
>> 그러나, NPE 에러가 발생하였다 :: Why?
- 왜냐하면 객체 p에 대한 해시값을 pdt에서 찾을 수 없었기 때문이다
- HashMap의 key값으로 클래스를 사용한다면, 해당 클래스에 반드시 hashCode()를 재정의해줘야 한다
import java.util.*;
class Product{
String name;
int price;
Product(String name, int price){
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object o){
if(this == o)
return true;
if(!(o instanceof Product))
return false;
Product p = (Product)o;
return Objects.equals(name, p.name) && price == p.price;
}
@Override
public int hashCode(){
return Objects.hash(name, price);
}
}
public class test{
public static void main(String[] args) {
Map<Product, Integer> pdt = new HashMap<>();
pdt.put(new Product("연필", 200), 10);
pdt.put(new Product("샤프", 500), 8);
pdt.put(new Product("볼펜", 1000), 5);
Product p = new Product("연필", 200);
int count = pdt.get(p);
System.out.println(count);
}
}
- hashCode()를 재정의하니까 "10"이라는 값이 제대로 출력되었다