프로그래밍/java

equals, hashCode와 @EqualsAndHashCode

승민아 2024. 6. 7. 17:00

테스트 코드 작성중에 특정 인자와 함께 메서드가 1회 실행되었는지 확인하는 과정이 있었다.

그런데 이상하게 인자가 잘 넘어갔는데 메서드가 테스트가 통과하지 않는 것이다.

 

동일한 객체인지 비교에 문제가 있어서 그런가 클래스에 @EqualsAndHashCode를 붙였더니

테스트 코드가 통과된다.

 

그래서 equals와 hashCode에 대해 이해해보자.

 

Object 클래스에 기본적으로 hashCode과 equals가 있다.

 

이건 어디다 쓰이는 것일까?

 

@Test
void test() {
    Car carA = new Car("12가3456");
    Car carB = new Car("12가3456");

    System.out.println("carA == carB : "  + (carA == carB));
    System.out.println("carA.equals(carB) : "  + (carA.equals(carB)));
}

class Car {
    
    String number;
    
    public Car(String number) {
    this.number = number;
    }
}

위의 출력 결과는 무엇일지 생각해보자.

 

결과

두개 모두 false가 출력된다.

 

carA == carB

객체에 대한 == 연산의 결과는

해당 객체를 가리키는 레퍼런스의 비교로 이뤄진다.

그래서 carA와 carB를 가리키는 레퍼런스는 다르기 때문에

결과가 false가 나온 것이다.

 

carA.equals(carB)

객체에 들어있는 값을 비교해서 두개의 객체가 같은지 equals 함수를 통해 비교할 수 있다.

 

equals 함수

equals 함수를 오버라이딩하여 객체의 값을 통해 비교가 가능해진다.

public class Car {
    String number;
    public Car(String number) {
        this.number = number;
    }

    @Override
    public boolean equals(Object obj) {

        Car car = (Car) obj;
        if(this.number.equals(car.number)) {
            return true;
        } else {
            return false;
        }
    }

}

 

equals를 오버라이딩하여 내가 객체의 비교 기준을 정의할 수 있다.

결과

equals를 number 필드의 비교로 재정의했더니 이제 equals는 true가 나온다.

 

equals 함수는 객체의 비교에 쓰인다는 것을 알았다.

즉, 두 객체의 내용이 같아서 동등한지를 비교하는 것이다.

 

hasdCode 함수

그러면 hashCode는 어디에 쓰는 것일까?

hashCode는 두 객체가 동일한지를 비교할때 사용한다.

 

아래 코드의 출력 결과를 보자.

Car carA = new Car("12가3456");
Car carB = new Car("12가3456");

System.out.println("carA = " + carA);
System.out.println("carB = " + carB);

 

결과

"클래스명@알 수 없는 값"의 형태로 나온다.

이때 "알 수 없는 값"이 객체의 해쉬값인 것이다.

 

이 객체가 가진 값을 떠나서 누구인지 식별하는. 똑같은 객체인지 동일성을 비교할때 사용되는 값이다.

 

이 해쉬 값은 hashCode의 결과로 반환되는데 아래와 같이 한번 재정의 해보자.

public class Car {

    @Override
    public int hashCode() {
        return 15;
    }
    
}

 

 

결과

우리가 정의한 값이 해쉬 값 16진수로 잘 나온다.

 

이 hashCode의 값은 어디에 쓰이는걸까?

자바의 HashMap, HashSet등 해시 값을 사용하는 자료구조 내부에서 객체의 비교에 해쉬 값을 사용한다.

이 값이 다르다면 다른 객체로

이 값이 같다면 같은 객체로 판명한다.

 

아래에 equals와 hashCode가 정의된 클래스를 보자.

public class Car {
    String number;

    public Car(String number) {
        this.number = number;
    }

    @Override
    public boolean equals(Object obj) {

        Car car = (Car) obj;
        if(this.number.equals(car.number)) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return 15;
    }

}

equals는 number가 같다면 true를

hashCode는 동일한 값 15를 반환하는 상황이다.

 

아래의 출력 결과를 예측해보자.

Car carA = new Car("12가3456");
Car carB = new Car("12가3456");

HashMap<Car, String> map = new HashMap<>();
map.put(carA, "carA");

String s = map.get(carB);
System.out.println(s);

carA 객체를 Key로 "carA"를 Value로 HashMap에 넣었다.

그 후 carB를 Key로 HashMap에 꺼낸다면?

 

결과

결과는 null을 예상했겠지만 "carA"가 출력된다.

hashCode와 equals가 동일한 값을 같기에 해시 기반인 자료구조에서

carB를 carA로 인식한 것이다.

 

이런 부분에서 해시 기반의 자료구조에서 hashCode를 주의해서 사용해야한다!

 

@EqualsAndHashCode

@EqualsAndHashCode 어노테이션은

객체의 필드를 이용해서 equals, hashCode 함수를 자동으로 생성해주는 어노테이션이다.

 

@EqualsAndHashCode
public class Car {
    String number;
    public Car(String number) {
        this.number = number;
    }
}

어노테이션을 붙여주기만하면 hashCode의 값을 생성해주고 equals도 생성해준다.

 

아래의 결과를 보자.

Car carA = new Car("12가3456");
Car carB = new Car("12가3456");

System.out.println("carA = " + carA);
System.out.println("carB = " + carB);
System.out.println("carA == carB : "  + (carA == carB));
System.out.println("carA.equals(carB) : "  + (carA.equals(carB)));

 

결과

필드를 비교해서 해쉬 값도 동일해지고, equals 동등성도 비교가 가능해진다.

 

"==" 연산 결과도 true가 나와야하는거 아닌가?

"=="의 연산 결과는 두 객체가 참조하는 메모리 주소가 같아야지 true가 나온다!