Java - Equals & HashCode


1. equals와 hashCode

  • equals와 hashCode 두 뿌리는 모두 Object이다.
    • Object와 Objects를 헷갈리는 사람들이 있는데 Object가 뿌리가 되는 기본 클래스이고, Objects는 Java 7부터 추가된 단순히 null 처리 혹은 비교와 같은 유틸리티성 기능을 제공하는 클래스를 말한다. 그래서 Object의 equals를 사용하며면 null이 있을 시 에러가 발생하고, Objects의 equals를 사용시 null이 있어도 에러가 발생하지 않는다.
  • equals 메서드는 두 객체의 참조 주소가 같은지를 비교해 주는 메서드이다. 즉, 동일성을 판단하는 == 와 같다.
  • hashCode는 객체의 주소값을 해싱 기법을 통해 16진수의 숫자로 만든 후 반환한 값이다.
  • Object에 정의 된 hashCode를 보면 아래 코드와 같다.
      public class Object {
         public native int hashCode();
      }
    
    • native 키워드가 들어간 메서드는 OS가 가지고 있는 메서드를 의미한다.
    • JNI (Java Native Interface) 는 C나 저수준의 언어로 작성된 native 코드를 JVM에 적재시키고 실행해주는 머신인데, 바로 이 native 코드중 하나가 hashCode() 메서드이다.
    • 이 네이티브 메서드는 OS에 C언어로 작성되어있어 그 안의 내용은 볼 수 없고, 오로지 사용만 할 수 있다.
    • 그래서 마치 추상 메서드 처럼 정의 되어 있는 것이다.



2. equals 와 hashCode는 재정의 할 수 있다

  • equals 와 hashCode는 재정의 오버라이딩 할 수 있다.
  • 오버라이딩한 대표적인 클래스가 바로 String이다.
  • equals는 객체 주소를 비교하는 메서드라고 설명했는데, String에서는 값을 비교한다. 그래서 우리가 동일성과 동등성을 설명할 때 String의 equals는 동등성의 대표적인 예시라고 말한다.
  • 이 처럼 필요에 따라 equals는 값을 비교하기 위해 재정의하여 사용한다.
  • 마찬가지로 hashCode도 같은 값일 경우, 같은 해시코드값을 참조하도록 재정의 할 수 있다.
  • 아래는 재정의 예시이다.
      class A {
          String data;
    
          public A(String data) {
              this.data = data;
          }
    
          public boolean equals(Object o) {
              if (this == o) return true; 
              if (!(o instanceof Person)) return false; 
              A a = (A) o; 
              return Objects.equals(this.data, a.data); 
          }
    
          public int hashCode() {
              return Objects.hash(data); 
          }
      }
    
      public class Main {
          public static void main(String[] args) {
              A a1 = new A("test");
              A a2 = new A("test");
    
              // equals() 와 hashCode() 를 오버라이딩 했기에 두 객체 필드 name의 해시코드가 반환되어 같다.
              System.out.println(a1.hashCode()); // 1234567
              System.out.println(a2.hashCode()); // 1234567
    
              // 따라서 순수 객체의 주소를 얻고 싶다면 identityHashCode() 를 사용해야 한다.
              System.out.println("System.identityHashCode(a1) : " + System.identityHashCode(a1)); // System.identityHashCode(a1) : 1234567
              System.out.println("System.identityHashCode(a2) : " + System.identityHashCode(a2)); // System.identityHashCode(a2) : 7654321
          }
      }
    
  • 참고로 인텔리제이와 같은 IDE에서는 자동으로 equals와 hashcode를 오버라이딩 해주는 기능을 제공한다. (직접 일일이 오버라이딩 할 필요가 없다. )



3. 왜 equals를 오버라이딩 하려면 꼭 hashCode도 함께 오버라이딩 해야하는가?

  • equals를 오버라이딩 할 때는 hashCode도 꼭 함께 오버라이딩 해줘야한다.
  • 왜냐하면 일반적인 값을 비교할 때는 equals만 재정의해도 문제가 없지만, Collections 같은 자료 구조에서 값을 비교할 때는 hashCode도 함께 비교하기 때문이다.
  • 자료구조에서는 hashCode를 먼저 비교한 뒤, 같으면 그 때 equals를 비교하고, hashCode가 다르면 그냥 다른 객체라고 판정한다.
    • 그래서 equals만 재정의했을 경우 HashSet처럼 중복을 허용하지 않는 자료구조에서 equals 값은 같은데 hashCode가 다르다고 중복 제거를 안하는 경우가 생길 수 있다.
    • 이러한 오류는 현업에서 잘 안보일 수 있기 때문에 치명적인 오류가 될 수 있다.



4. 해시코드 값는 고유한가?

  • 별로 볼 일은 잘 없겠지만, 결론부터 말하면 고유하지 않다. 숫자가 길어서 같은 hashCode가 생성될 일은 적지만 실제로 가능은하다. 그렇기에 hashCode만을 가지고 객체가 같은지를 비교해서는 안된다.
  • 그럼 왜 이렇게 만들었을까? 그건 hashCode가 같으면, 이후 equals메서드로도 두 객체의 진짜 주소를 직접적으로 비교하게끔 순서가 되어있기 때문에 hashCode가 같은건 두 객체를 비교할 때 문제가 되지 않는다. ( 이 역시 위에서 이야기한 hashCode, equals를 통한 객체 비교 순서와 같다 )
  • 또한 hashCode로 비교하는게 훨씬 빠르기 때문에 이렇게 먼저 hashCode로 비교하는 것이다.



5. 오버라이딩 한 후에는 원래 내가 원하는 equals와 hashCode의 메서드는 사용할 수 없는걸까?

  • equals 대신 ==을 사용하면 된다.
  • hashCode 대신 identityHashCode 을 사용하면 된다.






results matching ""

    No results matching ""