문제의 시작
동일성과 동등성은 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 기반 자료구조를 사용할 때는 equals와 hashCode의 계약을 함께 지켜야 한다. 값 객체를 만들수록 이 기본기는 더 중요해진다.
equals() 를 구현할 때 null-safe (null argument 일 때의 exception 처리) 를 하는 것이 중요하다.
결론 :
- primitive data type 에는 ‘==’ 를 사용하자
- 객체 비교에는 equals() 를 사용하자.
- equals() 와 hashCode() 를 함께 override 하자.
- ‘==’ 를 String 에서 사용하지 말자. (String 은 object이기 때문)
- equals() override 에서 null check 를 반드시 하자.