-> 블로그 이전

[Java] hashCode() - equals() 재정의

2022. 2. 20. 21:53Language`/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"이라는 값이 제대로 출력되었다