멈재

[JAVA] 20. 동일성(identity)과 동등성(equality) 본문

JAVA & Spring & JPA

[JAVA] 20. 동일성(identity)과 동등성(equality)

멈재 2023. 1. 8. 23:31
728x90

어떠한 두 대상이 같은지 여부를 확인하는 과정에서 흔히 동일성 비교동등성 비교를 하게 된다.
다만, 경우에 따라서 메모리 내 주소값이 달라도 특정 조건을 만족하면 논리적으로 같은 객체로 다뤄야 하는 경우가 존재한다.

동일성(identity): 메모리 내 주소값이 같은지 비교한다. (==)
동등성(equality): 논리적 지위가 동등한 지 비교한다. (equals)


우선 동일성 비교(==)부터 알아보자.

int, boolean과 같은 원시 타입간에 동일성 비교는 값을 비교한다.

boolean isTrue = true;
boolean isFalse = false;

System.out.println(isTrue == isFalse); // true == false >> false
System.out.println(isTrue == true);    // true == true >> true

int zero = 0;
int one = 1;

System.out.println(zero == one); // 0 == 1 >> false
System.out.println(zero == 0);   // 1 == 1 >> true


참조 타입에 대해서는 주소값을 비교한다.

String jae1 = "jae";
String jae2 = "jae";
String newJae = new String("jae");
System.out.println(jae1 == jae2);   // 0x100 == 0x100 >> true
System.out.println(jae1 == newJae); // 0x100 == 0x200 >> false

Person mum1 = new Person("mum");
Person mum2 = new Person("mum");

System.out.println(mum1 == mum2); // 0x101 == 0x201 >> false

동일성 비교는
원시형 타입간의 비교는 값을 비교하고,
참조 타입간의 비교는 주소값을 비교한다.



다음으로는 동등성 비교이다.
동등성 비교(equals)는 동일성 비교(==)와 달리 객체에 대해서만 사용이 가능하다.

모든 객체의 조상인 Object 클래스의 equals 메서드를 보자.

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

반환 형태를 보면 알 수 있듯이, 단순히 동일성 비교를 하고 있다.
즉, 해당 메서드를 재정의하지 않을 경우 equals 연산자는 == 연산자와 다르지 않게 된다.

public class EqualsEx1 {
    public static void main(String[] args) {
        Disney lionKing1 = new Disney("lion king");
        Disney lionKing2 = new Disney("lion king");

        System.out.println(lionKing1.equals(lionKing2)); // 0x100 == 0x200 >> false
    }
}

class Disney {
    String name;

    public Disney(String name) {
        this.name = name;
    }
}

위 예시 코드처럼 말이다.

이름이 같은 영화는 존재해서는 안된다라는 요구사항이 존재한다면 다음과 같이 equals 메서드를 재정의 해주어야 한다.

public class EqaulsEx1 {
    public static void main(String[] args) {
        Disney lionKing1 = new Disney("lion king");
        Disney lionKing2 = new Disney("lion king");

        System.out.println(lionKing1.equals(lionKing2)); // true
    }
}

class Disney {
    String name;

    public Disney(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        Disney disney = (Disney) o;
        return this.name.equals(disney.name);
    }
}


만약 Hash 값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때에는 위와 같이 해줄 경우 문제가 발생한다.
그 이유는 위 세 컬렉션은 객체가 논리적으로 같은지 비교할 때 다음과 같은 과정을 거친다.

이미지 출처: 테코블
hashCode 메서드의 반환 값이 같은 지를 우선 일치하는지 확인하고,
equals 메서드의 리턴 값이 true여야 논리적으로 같은 객체라고 판단한다.

이처럼 hashCode 메서드가 재정의 되어있지 않는다면 두 Disney 객체는 서로 다른 hashCode 메서드의 반환 값으로 인해 다른 객체로 판별되기 때문이다.

이 문제를 해결하려면 equals 메서드와 hashCode 메서드를 재정의 해주어야 한다.

public class EqualsAndHashCodeEx {
    public static void main(String[] args) {
        Set set = new HashSet();

        Disney lionKing1 = new Disney("lion king");
        Disney lionKing2 = new Disney("lion king");

        set.add(lionKing1);
        set.add(lionKing2);

        System.out.println(set);    // [Disney{name='lion king'}]
        System.out.println(lionKing1.equals(lionKing2)); // true
    }
}

class Disney {
    String name;

    public Disney(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        Disney disney = (Disney) o;
        return this.name.equals(disney.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }

    @Override
    public String toString() {
        return "Disney{" +
                "name='" + name + '\'' +
                '}';
    }
}


그렇다면 String의 경우에는 어떨까?

String mumjae1 = new String("mumjae");
String mumjae2 = new String("mumjae");

System.out.println(mumjae1.equals(mumjae2)); // true

신기하게도 기존 객체들과는 달리 String 간의 동등성 비교는 true를 반환한다.
이것이 가능한 이유는 String 클래스의 equals 메서드는 기본적으로 재정의가 되어있기 때문이다.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value, aString.value)
                              : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}



참고
https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode/
https://brunch.co.kr/@mystoryg/132
https://steady-coding.tistory.com/534