Java/이론&문법

Java equals() vs hashCode()

Bubbles 2023. 8. 8. 19:43

급 tmi.  intellij에서 cmd + option + b 누르면 docs 열 수 있음 

# 1

equals()와 hashCode() 차이가 무엇일까요? 

그리고 이 메소드들이 자바의 해시 컬렉션의 성능에 어떤 영향을 끼칠까요? 

 

equals()는 참조 변수의 값이 같은지 비교하는 메소드로, 재정의 되어있지 않다면 객체의 주소값을 비교하여 동일 객체인지 비교후 boolean 값을 리턴하는 메소드입니다.

대표적으로 String 클래스에 equals를 사용할 경우에는 주소값이 아닌 데이터 값을 비교하여 동일 여부를 판정합니다. (아래 코드 참고) 

hashCode()는 객체의 주소값을 이용하여 해싱 기법을 적용한 결과값을 리턴하는 메소드입니다. 

자바의 해시 컬렉션은 동일 객체인지 판정할 때 hashCode() 값이 같은지 먼저 확인하고 같다면 equals()함수를 호출하여 2번의 확인 작업을 거칩니다.

equals()만 오버라이딩할 경우, equals()로 객체를 비교할 때에는 동일하다고 판정하였으나 hashCode() 값이 달라 다른 객체로 판단되어 hash Collections에 두 객체 모두 추가되는 등 원치 않은 방향으로 동작할 수 있기 때문에 사용하려는 객체에 따라 equals()와 hashCode() 두 메소드 모두 재정의해주는 과정이 필요합니다. 

 

 

String class의 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;
    }

hashCode() 

- String class의 경우 각 글자 기반으로 해싱하는 것을 확인할 수 있다. 

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

따라서, string 대상으로 코드를 작성해보면 아래와 같은 결과를 얻을 수 있다. 

String a = "Hello";
String b = new String("Hello");
String c = new String("Hello");

System.out.println(a == b); // false
System.out.println(b == c); // false
        
System.out.println(a.equals(b)); // true
System.out.println(b.equals(c)); // true
// a, b, c hashcode 찍어보면 동일

 

 

만약 내가 직접 만든 클래스를 사용하겠다고 가정해보자. 

equals()와 hashCode() 모두 재정의하지 않았기 때문에 아래와 같이 모두 false가 나오는 걸 확인할 수 있다. 

MyClass myClass1 = new MyClass("Hello");
MyClass myClass2 = new MyClass("Hello");
System.out.println(myClass2 == myClass1); // false
System.out.println(myClass2.equals(myClass1)); // false
// hascode 값도 다르게 나온다

static class MyClass {
	String name;
	MyClass(String name) {
		this.name = name;
	}
}

 

 

IntelliJ에서는 cmd + N 단축키 가지고 손쉽게 오버라이딩할 수 있다. 

이렇게 재정의하고 코드를 다시 실행해보자.

static class MyClass {
        String name;
        MyClass(String name) {
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            MyClass myClass = (MyClass) o;
            return Objects.equals(name, myClass.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
}

아래와 같은 결과를 얻을 수 있다. 

System.out.println(myClass2 == myClass1); // false
System.out.println(myClass2.equals(myClass1)); // true
System.out.println(myClass1.hashCode() == myClass2.hashCode()); // true