728x90

안녕하세요

오늘은 Java의 equals() 메소드에 대해 알아보겠습니다.

 

equals()는 내용이 같은지를 비교하는 것이고 ==은 같은 객체인지 확인하는 것이라는 말을 많이 들었을 것입니다.

정말 그럴까요?

 

우선 Java에서 ==은 같은 객체인지 아닌지를 비교하는 연산자입니다. (primitive 타입의 경우 값 비교)

 

우선 Object 클래스의 equals() 메소드를 한 번 살펴봅시다.

Object 클래스의 equals() 메소드는 ==으로 비교하는 것을 볼 수 있습니다.

즉, Object 클래스는 ==으로 비교하는 것과 equals() 메소드로 비교하는 것이 똑같다는 것입니다.

정말 그런지 한 번 확인해볼까요?

public class ObjectTest {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = o1;
        Object o3 = new Object();
        Object o4 = new Object();
        System.out.println((o1 == o2) + " " + o1.equals(o2));
        System.out.println((o1 == o3) + " " + o1.equals(o3));
        System.out.println((o1 == o4) + " " + o1.equals(o4));
        System.out.println((o2 == o3) + " " + o2.equals(o3));
        System.out.println((o2 == o4) + " " + o2.equals(o4));
        System.out.println((o3 == o4) + " " + o3.equals(o4));
    }

}

o1과 o2는 같은 객체입니다.

 

이 코드를 실행해보면

==으로 비교한 것과 equals() 메소드로 비교한 결과가 모두 같습니다.

또한 같은 객체인 o1과 o2만 true가 나온 것을 볼 수 있습니다.

 

그럼 이제 제가 클래스를 하나 만들어보겠습니다.

 
public class Person {

    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

}

이름과 나이를 상태로 가지는 Person이라는 클래스를 만들었습니다.

 

public class EqualsTest {
    public static void main(String[] args) {
        Person p1 = new Person("김땡땡", 20);
        Person p2 = new Person("김땡땡", 20);
        Person p3 = p2;
        Person p4 = new Person("이땡땡", 25);
        System.out.println(p1.equals(p2));
        System.out.println(p1.equals(p3));
        System.out.println(p1.equals(p4));
        System.out.println(p2.equals(p3));
        System.out.println(p2.equals(p4));
        System.out.println(p3.equals(p4));
    }
}

p1과 p2는 이름과 나이가 모두 같고, p2와 p3는 같은 객체입니다.

p4는 p1, p2, p3과 다른 객체이고 내용도 다릅니다.

 

그리고 이 코드를 실행하면 어떤 결과가 나올까요?

.

.

.

.

.

.

.

.

.

.

.

.

.

.

결과는 이렇습니다.

같은 객체인 p2와 p3의 비교에서만 true를 리턴했습니다.

 

p1과 p2는 나이와 이름이 모두 같은데 왜 equals()로 비교를 했을 때 다르다고 나왔을까요?

 

사실 이는 당연한 결과입니다. Object 클래스의 equals()는 ==으로 비교한 것과 똑같은 것을 확인했었죠?

제가 만든 Person 클래스는 Object 클래스를 상속받기 때문에 Object의 equals()와 동일합니다.

따라서 Person 객체들을 ==으로 비교한 것과 결과가 똑같이 같은 객체인 경우에만 true를 반환했던 것입니다.

 

그런데 저는 같은 객체일 때가 아니라 이름과 나이가 모두 동일할 때 true를 반환하고 싶습니다.

그러면 어떻게 해야 할까요?

 

이름과 나이가 같으면 true를 반환하도록 equals() 메소드를 오버라이딩(재정의) 해주면 됩니다.

@Override
public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;
        if (p.getName() != null && p.getName().equals(this.name) && p.getAge() == this.age) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

저는 이렇게 작성해보았습니다.

 

equals() 메소드를 오버라이딩 했으니 이제 아까 코드를 다시 실행해볼까요?

public class EqualsTest {
    public static void main(String[] args) {
        Person p1 = new Person("김땡땡", 20);
        Person p2 = new Person("김땡땡", 20);
        Person p3 = p2;
        Person p4 = new Person("이땡땡", 25);
        System.out.println(p1.equals(p2));
        System.out.println(p1.equals(p3));
        System.out.println(p1.equals(p4));
        System.out.println(p2.equals(p3));
        System.out.println(p2.equals(p4));
        System.out.println(p3.equals(p4));
    }
}
이제 실행결과가 어떻게 나올까요?

이렇게 이름과 나이가 같은 p1과 p2의 비교에서 true를 리턴한 것을 볼 수 있습니다.
또한 p3는 p2와 동일한 객체이기 때문에 p1과 p3의 비교에서도 true가 나왔습니다.

 

그런데 equals() 메소드를 오버라이딩할 때 주의할 점이 있습니다.

이것은 Object 클래스의 equals() 메소드에 달린 주석입니다.
다른 객체가 이것과 같은지 아닌지를 나타냅니다.
equals() 메소드는 null이 아닌 참조 객체에 대해 비교를 수행합니다.
재귀적이다. null이 아닌 참조 타입 객체 x가 있을 때 x.equals(x)는 true를 반환해야 한다.
대칭적이다. null이 아닌 참조 타입 객체 x. y가 있을 때 y.equals(x)가 true일 때만 x.equals(y)가 true여야 한다.
전이적이다. null이 아닌 참조 타입 객체 x, y, z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)는 true를 반환해야 한다.
일관적이다. null이 아닌 참조  타입 객체 x, y가 있을 때 x.equals(y)를 여러 번 호출해도 일관적으로 계속 true를 반환하거나 일관적으로 계속 false를 반환해야 한다.
null이 아닌 참조 타입 객체 x가 있을 때, x.equals(null)은 false를 리턴해야 한다.

(의역이 있을 수 있습니다.)

equals() 메소드를 오버라이딩 할 때는 위 규칙들을 반드시 지키며 재정의해야 합니다.

 
또한 equals() 메소드를 오버라이딩할 땐 hashCode() 메소드도 오버라이딩하는 것이 좋습니다.
그렇지 않으면 HashSet이나 HashMap을 사용할 때 원치 않는 결과를 얻을 수 있습니다.

무슨 말인지 아직 와 닿지 않으시죠? 한 번 예를 들어보겠습니다.

아까 제가 구현한 Person 클래스를 key로 가지는 HashMap을 만들어보겠습니다.

import java.util.HashMap;
import java.util.Map;

public class HashMapTest {
    public static void main(String[] args) {
        Map<Person, Integer> map = new HashMap<>();
        Person p1 = new Person("김땡땡", 20);
        Person p2 = new Person("김땡땡", 20);
        map.put(p1, 1);
        map.put(p2, 2);
        System.out.println(map.size());
    }
}
아까 저희는 이름과 나이가 같으면 true를 리턴하도록 equals() 메소드를 재정의했습니다.

이름과 나이가 같은 객체 p1과 p2를 만들고 map에 한 번은 p1을 key로, 한 번은 p2를 key로 엔트리를 추가했습니다.

이 코드의 결과는 어떻게 될까요?
1을 출력할까요? 2를 출력할까요?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2를 출력합니다.
이상하지 않나요?
key 값이 같으니 덮어써질 것 같은데, 왜 다른 key로 인식하는 것일까요?
그 이유는 hashCode()를 재정의하지 않았기 때문입니다.
 
Object 클래스의 equals() 메소드에 달린 주석
같은 객체는 같은 해시코드를 가져야 한다는 hashCode() 메소드의 일반적인 규칙을 유지하기 위해서 equals() 메소드를 오버라이딩 했을 때는 일반적으로 hashCode()를 반드시 오버라이딩해야 한다.
위에 써있는 것처럼 equals() 메소드를 오버라이딩 했다면 equals() 메소드 비교에서 true를 반환하는 두 객체는 hashCode() 메소드에서도 같은 해시코드를 반환해야 합니다.
HashMap은 equals() 메소드와 hashCode() 메소드 모두 같아야 동일한 key로 인식합니다.
따라서 위 코드의 실행 결과 1을 출력하기를 원한다면 hashCode()를 오버라이딩하여 hashCode()의 리턴값까지 같게 만들어줘야 합니다.
한 번 hashCode() 메소드를 오버라이딩해보겠습니다.
@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
}
이렇게 hashCode() 메소드까지 오버라이딩 했으니 다시 아까 코드를 실행해볼까요?
import java.util.HashMap;
import java.util.Map;

public class HashMapTest {
    public static void main(String[] args) {
        Map<Person, Integer> map = new HashMap<>();
        Person p1 = new Person("김땡땡", 20);
        Person p2 = new Person("김땡땡", 20);
        map.put(p1, 1);
        map.put(p2, 2);
        System.out.println(map.size());
    }
}

이제 1을 출력하는 것을 볼 수 있습니다.

 

모든 클래스가 가지고 있는 equals() 클래스와 주의점에 대해서 알아보았습니다.

읽어주셔서 감사합니다.

728x90

+ Recent posts