프로그래밍/Java

[Java] JDK 부수기 - (1) java.lang.Object - 2. equals

Churnobyl 2023. 11. 20. 14:45
728x90
반응형

 


2. equals(Object obj)

또 다른 객체 obj가 해당 객체와 동등(equal to)한지 보여준다.

 

JVM 뜯어보기

 

 Object.equals() 메서드는 null이 아닌 객체와 다른 객체 간의 동등성을 평가하는 메서드다. 구현 코드 자체는 굉장히 단순하다. 아래를 보자.

 

// src/java.base/java/lang/Object.java

public class Object {

    public boolean equals(Object obj) {
            return (this == obj);
        }
        
}

 

 equals() 메서드는 네이티브 코드가 아닌 일반적인 자바 코드로 되어 있다. this == obj를 통해 해당 객체와 파라미터로 넘겨 받은 obj 객체의 메모리 주소가 같은지 비교해 boolean으로 리턴해 준다. 코드대로라면 어떤 다른 두 객체를 equals()로 비교했을 때, 객체 각각의 주소는 다르므로 항상 false가 나온다. 두 객체의 메모리 주소가 다름을 확인하기 위해 이러한 결과를 의도했다면 문제없지만, 일반적으로 두 객체의 값을 같은지 비교하는 상황에서 항상 false가 나오는 결과를 바라지는 않을 것이다. 그래서 대부분의 경우 클래스를 작성할 때 이 equals() 메서드를 오버라이딩해서 사용한다. 대표적인 예가 문자열을 표현하는 String 클래스다.

 

// src/java.base/java/lang/String.java

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {

    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            return (anObject instanceof String aString)
                    && (!COMPACT_STRINGS || this.coder == aString.coder)
                    && StringLatin1.equals(value, aString.value);
        }
        
}

 

 String 클래스의 equals() 메서드만을 가져왔다. String 클래스는 equals() 메서드를 그대로 사용하지 않고 객체가 가진 변수 값을 직접적으로 비교하도록 오버라이딩해서 사용한다. 코드를 살펴보자. 일단 if문을 통해 해당 객체와 anObject객체의 메모리 주소가 같다면 두 객체는 완전히 동일한 객체이므로 true를 리턴하도록 했다. 그 다음이 중요한데, Java 14에서 도입된 패턴 매칭 기능을 활용해 anObject 객체가 String의 인스턴스인지 확인하고 만약 true라면 aString이라는 새로운 변수에 객체를 할당한다. 그리고 이어서 인코딩 관련 플래그인 COMPACT_STRINGS 그리고 인코더 정보인 coder를 비교해 같은 인코딩을 사용하는지 확인한 뒤 최종적으로 실제 value로 두 문자열이 동일한지 비교한다. String 클래스는 이 세가지 조건이 모두 충족될 때만 true를 리턴해 두 문자열이 같다는 것을 효율적으로 확인하는 equals() 메서드가 될 수 있도록 만들었다. 이처럼 클래스에 equals() 메서드가 필요할 때는 주로 객체의 값을 직접적으로 비교하도록 오버라이딩한다는 사실을 기억하자.

 

 이제 실제로 equals() 메서드를 사용해보자.

 

사용해보기

 

public class Person {
    int id;

    public Person(int id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}
public class Main {
    public static void main(String[] args) {
        Person person_1 = new Person(988515);
        Person person_2 = new Person(988515);

        System.out.println(person_1 == person_2);
        System.out.println(person_1.equals(person_1));
        System.out.println(person_1.equals(person_2));

        System.out.println(person_1);
        System.out.println(person_2);
    }
}
false
true
false
Person@1698c449
Person@5ef04b5

 


 person_1, person_2는 각각 988515라는 같은 id값을 가진 인스턴스다. 따라서 두 객체를 비교했을 때 일반적으로 true가 출력되기를 기대하지만 결과는 보다시피 false가 출력됐다. equals() 메서드를 오버라이딩하긴 했지만 Object의 메서드를 그대로 가져왔으므로 equals() 메서드는 person_1 == person_2와 사실상 같은 코드다. 그리고 person_1과 person_2는 개별 객체이므로 16진수 해시코드도 다른 것을 알 수 있다. 이제 equals() 메서드를 수정해서 메모리 주소가 아니라 객체가 가진 id값을 비교하도록 하자.

 

public class Person {
    int id;

    public Person(int id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;

        if (obj instanceof Person obj2)
            return this.id == obj2.id;

        return false;
    }
}
public class Main {
    public static void main(String[] args) {
        Person person_1 = new Person(988515);
        Person person_2 = new Person(988515);

        System.out.println(person_1 == person_2);
        System.out.println(person_1.equals(person_1));
        System.out.println(person_1.equals(person_2));

        System.out.println(person_1);
        System.out.println(person_2);
    }
}
false
true
true
Person@1698c449
Person@5ef04b5

 

 Person 클래스의 equals() 메서드를 수정했다. 첫번째 if문을 통해 해당 객체와 obj 객체의 메모리 주소가 동일하다면 true를 리턴하도록 했고, 두번째 if문을 통해 obj가 Person 클래스의 인스턴스라면 obj2에 할당한 뒤 두 객체의 id값을 비교해서 같다면 true를 리턴하도록 했다. 두 if문을 통과했다면 두 값은 다르므로 false를 리턴하도록 했다.

 

 결과를 보면 다음과 같이 equals() 메서드를 사용했을 경우 두 객체의 메모리 주소는 다르지만 서로의 id값은 같으므로 true가 출력됐다.

 

 

hashCode와의 관계

 만약 클래스에 equals() 메서드를 오버라이딩했다면 hashCode() 메서드도 함께 오버라이딩해야한다. 그것은 hashCode() 메서드의 설명을 보면 알 수 있다.

 

If two objects are equal according to the equals method, then calling the hashCode method on each of the two objects must produce the same integer result. - hashCode() Description 중에서
만약 두 객체가 equals() 메서드에 따라 동등하다면, 두 객체 각각의 hashCode() 메서드 사용 결과는 항상 같은 정수 값을 반환해야 한다.

 

 즉 equals() 메서드로 두 객체가 동일하다면 해시값도 같아야 한다는 규칙이다. hashCode() 메서드에 대한 더 자세한 내용은 다음 글에서 작성하도록 하겠다.

반응형