동등성과 동일성

SeokHwan An·2023년 3월 13일
0

java

목록 보기
5/10

사진의 플링글스는 모두 같다고 할 수 있을까? 아니면 모두 다를 것으로 봐야하는 것일까?

자바에서도 비슷한 느낌을 주는 개념이 있습니다. 이는 바로 동등성과 동일성 입니다.
동등성과 동일성은 저에게 항상 헷갈려 했던 주제였고 시간을 내어서 정리를 해보는 시간을 가져보려고 합니다.

동등성과 동일성

동등성은 흔히 논리적인 동치라고 표현합니다. 여기서 말하는 논리적인 동치는 같은 상태를 가지는 객체라고 이해하면 좋을 것 같습니다.

예를 들면 시중에서 플링글스 오리지널 맛이 두개가 있으면 서로 같은 것이라고 하는데 그렇게 하는 이유는 같은 이름, 같은 맛을 가지고 있기 때문입니다. 이렇게 같은 상태를 가지는 두개가 있을 때 같다고 하는 것이 동등성 입니다.

동일성은 메모리 주소 값을 비교해서 같은지 판별하는 것입니다. 즉 같은 상태(이름, 맛)을 가지고 있다고 한들 참조하는 메모리 주소가 다른 경우 서로 다른 것으로 판단합니다.

자바에서는 equals 메소드를 통해 동등성 비교를 하고 == 연산자를 통해 동일성 비교를 할 수 있습니다.

이를 String, Integer, 그리고 Object에 대해서 간단하게 확인해보도록 하겠습니다.

String에서의 동등성과 동일성

public class Equality {

    public static void main(String[] args) {
        String a = "플링글스";
        String b = "플링글스";
        String c = new String("플링글스");

        //동등성 비교
        System.out.println(a.equals(b)); //true
        System.out.println(a.equals(c)); //true

        //동일성 비교
        System.out.println(a == b); //true
        System.out.println(a == c); //false
    }
}

String에서의 동등성 비교(equals)의 경우 모두 같은 속성(”플링글스”)을 가지고 있기에 모두 true가 반환됩니다. 하지만 동일성 비교에서는 a와 b는 같은 주소 값을 가지는 것으로 판별이 되고 a와 c는 서로 다른 주소 값을 가지는 것으로 판별이 됩니다. 그 이유를 설명하기 위해서는 문자열 상수 풀에 대해 알아야합니다.

💡문자열 상수 풀(String Constant Pool)
문자열 상수 풀은 JVM 내 힙 영역에 위치한 공간으로 문자열 리터럴을 보관하는 용도로 이용됩니다.
이미 존재하는 문자열에 대해서는 같은 참조값을 가지게 합니다.

이와 같이 a가 먼저 생성이 될 때 문자열 상수 풀에 저장이 되고 b는 a와 같은 리터럴 문자열이기 때문에 같은 참조값을 가지게 됩니다. 그래서 동일성 비교(==)를 할 때 true를 반환하게 됩니다. 반면 c의 경우에는 생성자를 통해 문자열을 생성하기에 독립적인 참조값을 가지게 됩니다. 그렇기에 동일성 비교(==)를 하는 경우에 false를 반환하는 것을 확인할 수 있습니다.

Integer에서의 동등성과 동일성

 public class Equality {

    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 1;
        Integer c = new Integer(1);

				//동등성 비교
        System.out.println(a.equals(b)); //true
        System.out.println(a.equals(c)); //true
				
				//동일성 비교
        System.out.println(a == b); //true
        System.out.println(a == c); //false
    }
}

Integer에서의 동등성 비교(equals) 역시 값은 상태를 가지고 있기에 모두 true가 반한되는 것을 확인할 수 있습니다. 동일성 또한 a와 b모두 같은 주소 값을 가지는 것으로 판별되고 있습니다. 이 부분이 의문점이었습니다. 문자열의 경우 문자열 상수 풀이 따로 존재했지만 Integer의 경우 따로 저장공간이 존재하지 않았기 때문입니다.

그럼에도 같은 참조값을 가진다고 판별되는 이유는 Integer의 경우 -128부터 127까지의 수가 캐싱되어 있기 때문입니다. 그래서 같은 참조 값을 가지는 것을 확인할 수 있습니다. 하지만 생성자를 통해 새롭게 생성하는 경우는 다른 참조 값을 가지게 됩니다.

 public class Equality {

    public static void main(String[] args) {
        Integer a = 200;
        Integer b = 200;
				
				//동일성 비교
        System.out.println(a == b); //false
    }
}

Object에서의 동등성과 동일성

public class Cookie {

    private final String name;

    public Cookie(String name) {
        this.name = name;
    }
}
public class Equality {

    public static void main(String[] args) {
        Cookie a = new Cookie("플링글스");
        Cookie b = new Cookie("플링글스");
				
				//동등성 비교
        System.out.println(a.equals(b)); //false

				//동일성 비교
        System.out.println(a == b); //false
    }
}

위와 같이 이름을 인스턴스 필드로 가지는 Cookie라는 클레스를 만들고 동등성과 동일성을 비교하는 작업을 했는데 모두 false가 나왔습니다. 여기서 동일성 비교(==)가 false가 나오는 것은 이해가 되지만 동등성 비교(equals)의 결과가 이해가 안될 수도 있습니다. 이러면 앞서서 말한 equals는 동등성 비교 연산이 아닌 것이라고 생각할 수 있는데 equals는 경우 Object에서는 == 동일성 연산자로 되어 있습니다.

Object에서의 equals

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

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;
}

String에서의 equals는 먼저 같은 참조값을 가지는지 비교를 하고 그 다음으로 값이 같은지 비교하는 방식으로 구현이 되어 있습니다.

Integer에서의 equals

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

Integer에서는 파라미터가 Integer인지 확인한 후 값이 같으면 true를 그렇지 않으면 false를 반환하는 것을 알 수 있습니다.

이렇듯 각각의 클래스는 공통된 eqauls를 이용하는 것이 아닌 각 타입에서 필요한 비교연산을 거친 후에 동등한지 아닌지를 판별하게 구현이 되어 있는 것을 볼 수 있습니다.

다시 돌아와 Cookie에서 상태(이름)이 같은 경우에 동등하다고 하고 싶은 경우에는 equals를 재정의 하면 됩니다. 모든 객체는 Object를 상속받고 있기 때문에 재정의가 가능합니다.

import java.util.Objects;

public class Cookie {
    ...

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Cookie cookie = (Cookie) o;
        return Objects.equals(name, cookie.name);
    }

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

위와 같이 equals를 메소드를 재정의 하면 속성(이름)이 같은 경우에 동등하다는 결과를 확인할 수 있습니다.

public class Equality {

    public static void main(String[] args) {
        Cookie a = new Cookie("플링글스");
        Cookie b = new Cookie("플링글스");
				
				//동등성 비교
        System.out.println(a.equals(b)); //true
    }
}

이번에는 동등성과 동일성에 대해 학습을 했는데 스스로 글로 정리하면서 헷갈려 했던 부분이 해소가 되었고 이 글을 읽는 분들도 쉽게 이해가 되었으면 합니다:)

0개의 댓글