문제의 시작

동일성과 동등성은 Java를 처음 배울 때 지나치기 쉽지만, 실제 bug는 이 구분을 놓친 곳에서 자주 나온다. 객체가 같은 instance인지, 같은 값으로 취급되어야 하는지는 전혀 다른 질문이다. 이 글은 ==, equals, hashCode가 각각 어떤 책임을 갖는지 정리한 기록이다.

동일성 (identity) 와 동등성 (equality) 는 자주 헷갈리는 개념이다. 전자는 Java 에서 (‘==’) operator 의 개념이고, 후자는 (equals()) method 개념이다.

Identity

우선 자바에서는 모든 객체가 참조로 접근된다. ‘==’ 연산자를 쓸 때는 좌우의 실제값이 아닌 reference 자체로 접근된다.

단, primitive type (int, char, float …) 에서는 실제값으로 접근된다.

reference 로 접근한다는 것은, 실제 값이 같을지언정 주소값이 다르다면 ‘==’ 연산에서는 false 가 나온다는 뜻이다.

구현하면서 확인한 흐름

Integer x = 100;
Integer y = 100;
System.out.println(x == y);

이런 코드에서는 false 가 출력될 것이다. 단, JVM 의 integer caching 에 따라 -128~127 까지의 값은 true 로 출력된다.

Equality

equals() method 는 logical equivalence 를 확인한다. 두 객체의 data 자체를 확인한다는 뜻이다.

이 method 를 클래스에서 override 하는 것도 가능하다. 내 뜻대로 equality 가 무엇인지 정의할 수 있다는 뜻이다.

이 때는 hashCode() 까지 override 하는것이 좋다. HashSet과 HashMap 등의 Java Collection 이 존재하여 양쪽의 method 를 모두 사용해야 제대로 작동하기 때문이다.

public class Person {
    private String name;
    private int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Override equals() method
    @Override
    public boolean equals(Object obj) {
        // Check if the object is compared with itself
        if (this == obj) {
            return true;
        }

        // Check if obj is an instance of Person
        if (obj == null || obj.getClass() != this.getClass()) {
            return false;
        }

        // Typecast obj to Person so that we can compare data members
        Person person = (Person) obj;

        // Compare the data members and return accordingly
        return name.equals(person.name) && age == person.age;
    }

    // Override hashCode() method
    @Override
    public int hashCode() {
        // Use a prime number to calculate the hash code
        int result = 17;
        result = 31 * result + name.hashCode();
        result = 31 * result + age;
        return result;
    }

    // Getters and setters for name and age
    // ...
}

구현 기준

객체 비교는 작은 문법 문제가 아니라 collection 동작과 domain modeling에 직접 연결된다. 특히 HashSet, HashMap처럼 hash 기반 자료구조를 사용할 때는 equalshashCode의 계약을 함께 지켜야 한다. 값 객체를 만들수록 이 기본기는 더 중요해진다.

equals() 를 구현할 때 null-safe (null argument 일 때의 exception 처리) 를 하는 것이 중요하다.

결론 :

- primitive data type 에는 ‘==’ 를 사용하자

- 객체 비교에는 equals() 를 사용하자.

- equals() 와 hashCode() 를 함께 override 하자.

- ‘==’ 를 String 에서 사용하지 말자. (String 은 object이기 때문)

- equals() override 에서 null check 를 반드시 하자.