ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체의 동일성과 동등성
    CS 2023. 7. 28. 00:50

     

    코드를 짜며 객체끼리 비교하는 데 ==연산자로 똑같다는 결과가 나오지 않는 경우가 있었다.

    분명 둘은 같은 값을 담고 있었는데도 말이다.

    그래서 찾아보니 객체의 동등성과 동일성이라는 개념이 있었다. 이에 대해 알아보자.

     

    동일성(Identity)

    완전히 같다는 의미, 두 객체가 완전히 같다는 것을 의미한다.

    그런데 이렇게 설명하면 뭔가 부족하다. 이를 위해 객체를 좀 더 살펴보자.

     

    [객체]

    객체는 기본적으로 생성 시 메모리에 주소를 갖게 된다.

    메모리에는 다양한 영역이 있지만 그중 Heap 영역에 클래스의 인스턴스들의 실제값이 저장된다.

    예를 들어 아래와 같은 코드가 있다

    Coffee coffee = new Coffee("star coffee");

    이때 생성된 coffee라는 변수명을 위한 공간은 메모리의 stack영역에 공간을 만들어 저장된다.

    (cf: 기본적인 변수를 생성할 경우 그 공간은 stack에 만들어진다.

    int a = 1;이라는 선언을 할 경우 변수 a를 위한 공간이 stack에 만들어지는 것이다.)

    하지만 그 변수에 담긴 값은 실제 값인 star coffee가 아니라 Heap 영역에 위치한 인스턴스의 주소값을 저장하는 것이다.

    (이렇게 값 자체가 아닌 참조하고 있는 주소를 담고 있는 경우 '참조 변수'라고 하는 듯 하다.)

    이처럼 우리가 객체를 변수에 할당할 때는 객체 자체가 아닌 객체가 저장되어있는 주소를 할당하는 것이다

     

    다시 동일성으로 돌아가자면,

    객체가 동일 하다는 것은 두 객체가 똑같은 주소값을 참조하고 있는 경우이다.

     

    동등성(Equality)

    이 역시도 기본적으로는 같다는 의미지만 객체끼리 비교할 경우 두 객체가 담고 있는 값이 같다는 뜻이다.

    아래와 같은 예시가 있다.

    Integer a = new Integer(1);
    Integer b = new Integer(1);

    이 경우 a와 b는 같은 값을 담고 있기에 동등하다고 할 수 있다.

    그러나 a == b를 하면 false가 반환된다. 왜냐면 두 객체는 담고 있는 값은 같아도 Heap 영역 내에서 각각 다른 주소를 가지고 있기

    때문에 서로 동일하지 않은 객체라고 보기 때문이다.

     

    [객체간 동등성을 비교하려면 사용할 것은?]

     

    바로 equals()라는 함수이다.

    그러나 이를 그대로 사용한다면? 아래와 같이 된다.

    a.equals(b); // false

    원인은 equals()의 기본 형태에 있다.

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

    즉, 이미 == 연산자를 쓰고 있으므로 객체 간 '동일성'을 체크하는 것이다.

    이를 방지하기 위해서는 각각의 타입별로 '동등성'을 비교하도록 equals를 오버라이딩해야한다.

     

    예를 들면 Integer의 비교는 이러하다.

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return 비교하려는 값 value == ((Integer)obj).intValue();
        }
        return false;
    }

    intValue()는 Integer라는 객체에서 int라는 내용물을 빼오는 함수이다. 따라서 값 자체를 비교하는 것이 되어 문제가 없다.

    cf) 기본 자료형인 int, char 같은 것들은 객체가 아니기에 변수 할당 시 참조 주소를 저장하는 게 아닌 값 자체를 저장한다.

    그래서 동일 비교를 해도 문제가 없다.

     

     

    Plus)

    그런데 여기서 의문점이 생겼다

    Integer a = 1;
    Integer b = 1;
    
    System.out.println(a == b); // true
    System.out.println(a.equals(b)); // true

    위 두 변수는 객체형 타입이니까 서로 다른 객체라는 생각이 들었다.

    그렇기에 값은 같아도 참조 주소가 별개라고 생각해 둘 다 false가 나와야 할 것 같았다.

    그러나 결과는 모두 다 true였다. 왜 그런걸까?

     

    빠른 습득을 위해 Chat GPT에게 물어보았다.

     

    나:

    자바에서 Integer 객체 타입의 변수 2개를 선언했어. 그리고 콘솔에 서로를 비교한 것을 출력했어. 그 내용은 다음과 같아.

     

    Integer a = 1;

    Integer b = 1;

    System.out.println(a == b); // true

    System.out.println(a.equals(b)); // true

     

    여기서 왜 비교의 결과가 true가 나올까?

    a b 객체이니까 참조하는 주소값이 서로 다른 것아니야?

     

    Chat GPT:

    Java에서 Integer 클래스는 Wrapper 클래스로서 기본 자료형 int를 감싸는 래퍼 클래스입니다.

    Java에서는 정수를 포장한 객체를 비교할 때 특별한 메커니즘을 사용합니다.

    Integer 변수 a와 b에 1이라는 값을 할당하였고, a와 b는 모두 Integer 객체입니다.

    이때, 자바는 특정 범위의 정수 값을 캐싱합니다. 정수 값 -128부터 127까지의 범위에 대해서는 캐싱을 하기 때문에,

    해당 범위 내의 정수 값은 이미 캐시된 객체를 재사용하게 됩니다. 따라서, 여기서 a와 b에 할당된 값인

    1은 -128부터 127까지의 범위 내에 있기 때문에 동일한 Integer 객체를 재사용하게 됩니다.

    그래서 a와 b는 동일한 객체를 참조하게 되며, 결과적으로 a == b와 a.equals(b)의 비교 결과 모두 true가 나오게 됩니다.

    하지만 캐싱은 -128부터 127까지의 범위에만 해당되므로, 값이 범위를 벗어나면 캐싱되지 않고 새로운 객체가 생성됩니다.

    따라서 정수 값이나 범위를 벗어난 정수 값을 가진 객체는 a == b a.equals(b) 비교 결과가 false 있습니다.

     

    실제로 127과 128을 가지고 테스트 해보았다. Chat GPT가 말한대로 였다!

     

    그래서 이번에는 String을 가지고 실험해보았다.

    String c = "나";
    String d = "나";
    
    System.out.println("문자열 연산 결과는 이것이다!\n-----------------------------");
    System.out.println(c == d);
    
    연산 결과:
    
    
    문자열 연산 결과는 이것이다!
    -----------------------------
    true

    문자열은 왜그런건지 의아했다. 그래서 또 질문해보았다.

     

    나:

    String도 객체인데 두 객체가 참조하는 주소값이 서로 다른 것 아닐까?

    동일하다고 나오는 걸까?

     

    ChatGPT:

    Java에서 String 리터럴은 문자열 상수 풀(String Pool)이라는 공간에 저장됩니다. String Pool은 Java에서 문자열을 재사용하기 위해 사용되는 특별한 저장 영역으로, 동일한 문자열 리터럴은 하나의 객체로 저장됩니다. 따라서 같은 문자열 리터럴을 가지고 있는 String 변수들은 동일한 객체를 참조하게 됩니다.

    문자열 리터럴을 사용하여 String 변수 c와 d에 "나"라는 값을 할당했습니다. 이 "나"라는 문자열은 String Pool에 저장되므로, c와 d는 동일한 String 객체를 참조하게 됩니다. 그래서 c == d의 비교 결과는 true가 나오게 됩니다.

    하지만 동작은 문자열 리터럴에만 해당되며, new 연산자를 사용하여 String 객체를 직접 생성한 경우에는 String Pool 사용하지 않고 새로운 객체를 생성합니다. 따라서 아래와 같이 new 연산자로 객체를 생성한 경우에는 참조하는 주소값이 다르게 나올 있습니다.

    String c = new String("나");
    String d = new String("나");
    System.out.println(c == d); // false

    이렇게 new를 사용한다면 위에서 본 사실들과 같다.

    String 역시 equals() 메서드를 오버라이딩하여 동등성 비교를 하게 끔 변경한 다음 연산에 적용해주어야 한다.

     

    cf) 리터럴: 메모리 주소 값 이런 것이 아닌 실제 변수에 담기는 값 자체를 가리킴,

    자세한 설명이 있으나 정말 쉽게 설명하자면 이런 느낌인듯 하다.

     

    우와! 호기심에 따라 직접 테스트 해본 덕에 새로운 사실을 알았다!

    앞으로도 자주 시도해보아야겠다.

     

    코딩을 하다 객체 간 비교를 할 일이 생긴다면 위 사실들을 잘 떠올리고 적용해보도록 하자 :)

     

     

    참고 자료:

    https://steady-coding.tistory.com/534

    https://hudi.blog/identity-vs-equality/

    https://6161990src.tistory.com/85#:~:text=%EA%B0%9D%EC%B2%B4%20%EC%A7%80%ED%96%A5%20%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C%20%EA%B0%9D%EC%B2%B4,%EB%90%9C%20%EA%B0%9D%EC%B2%B4%20%EA%B3%B5%EA%B0%84%EC%9D%B4%20%EB%A7%8C%EB%93%A4%EC%96%B4%EC%A7%84%EB%8B%A4.

    ChatGPT

    https://velog.io/@pjeeyoung/리터럴

     

    'CS' 카테고리의 다른 글

    서버에서 '세션'이란?  (0) 2023.07.25
    템플릿 메서드 패턴 and 팩토리 메서드 패턴  (0) 2023.06.25
Designed by Tistory.