java string 클래스와 불변 객체

코딩/Java 2020. 9. 22. 00:33

  네이버 이웃 블로그 글을 보다가 개발자의 70%가 틀리는 문제 라는 제목을 가진 어떤 글을 보았습니다. 사실 과장이 되었는지 아닌지는 모르겠지만, 헷갈리는 것 중 하나임은 분명해 보입니다. 언어는 파이선이였습니다만, 자바로 해도 정답률이 비슷하지 않았을까 싶기도 합니다.

 


 이 코드는 어떻게 동작할까요? 먼저, toUpperCase는 String을 대문자로 바꾸는 메서드입니다. 예를 들어, "abcde".toUpperCase()의 리턴값은 "ABCDE"가 됩니다. 이건 그렇게 크게 어렵지는 않습니다. 여기서 출력값은 아래와 같이 됩니다.

 

 

  분명한 것은 우리가 예상했던, ABCDE, BCDEF가 나오지 않았음을 알 수 있습니다. 왜 그럴까요?

 


  코드를 뜯기는 상당히 기니, identityHashCode 값을 보도록 하겠습니다.

 

 System.identityHashCode는 객체의 해시값을 돌려줍니다.

 

 

 hashCode하고 다른 점은, 해시 코드가 오버라이드가 되던 말던, default hashCode 메서드를 리턴해 버립니다. 링크에서 언급드렸다 시피, 다른 객체라고 다른 해시 값이 나온다는 보장은 없습니다. 그런데, hashCode javadoc에 따르면 다음과 같이 나와 있습니다.

 

 

 2번째 줄을 보면, 보통은, distinct Object에 대해서, distinct Integer를 리턴한다고 나와 있습니다.

 

 

 list에 있는 String k와 k.toUpperCase()의 identityHashCode를 출력해 보았습니다. 그랬더니, 다른 값이 나왔습니다. 만약에, 같은 객체에 대해서 연산이 이루어 져서, 그 객체에 반영이 되었다면, for loop를 돌 때 마다 같은 Integer 쌍이 찍혔을 겁니다. 그런데, 그렇지 않았다는 이야기는, 새로운 객체가 생성이 되었다는 겁니다. 그러면 정확하게 어떤 일이 일어났을까요?

 

 


  먼저, 동적 배열에 2개의 String이 들어가 있을 겁니다.

 

 

 다음에, 루프를 돌면서, 각 String에 대해서 toUpperCase 메서드를 수행할 겁니다. 그런데, 이것을 수행하고 난 결과가, 기존에 리스트에 있었던 코드값과 달랐습니다. 이 이야기는, 기존에 리스트에 있었던, "abcde", "bcdef"와 다르다는 이야기입니다. 1번째 루프를 돌았을 때 상황을 보도록 하겠습니다.

 

 

 

  먼저, toUpperCase의 결과값 "ABCDE" 객체가 리턴이 됩니다. 그리고 이것을 for loop 내부에서 씁니다.

 

 

 10번째 줄은, 그러한 역할을 합니다. 단지, 변환된 새로운 객체 String을 리턴할 뿐입니다. 문제는, 이 리턴값을 쓰지 않았기 때문에, 쓰지 못하는 값이 되어 버립니다.

 

 

 그렇다면, 다음 상황은 이렇게 그려질 겁니다. 중요한 것은, li의 0번째 String 객체였던 "abcde"는 아무런 변화가 없었다는 것입니다. 단지, 변화된 결과 값이 복사되어서 리턴이 되었습니다. for loop를 돌면서, 그 어디에도, i번째 원소에 대입이 되는 코드가 없습니다. String이 불변 객체이고, 변화된 결과는 복사된 객체에 반영이 되므로, 원본에는 변화가 없습니다.

 

 따라서, 리스트의 요소가 가리키는 String이나, 내용은 변화가 없고, "ABCDE"는 가비지 콜렉터의 수거 대상이 됩니다.

 


  추가 질문을 드리겠습니다. 실행 결과는 어떻게 나올까요?

 

 이것도 만만치 않게 어려워 보입니다. 심지어 중간에 대입 코드가 있는데.. 천천히 그려보겠습니다. 8번째 줄의 for each문을 돌 겁니다. 1번째로 돌았을 때, 상황은 아래와 같을 겁니다.

 

 

 우리가 li.get(0)을 해서 string을 가리키는 k에 저장했다면, 위와 같은 그림이 그려질 겁니다. 다음에, 9번째 줄을 수행한 후에는 어떻게 바뀔까요? 스텝 바이 스텝으로 생각해 봅시다. 먼저, toUpperCase가 끝나고 난 직후의 상황은 아래와 같을 겁니다.

 

 

 새로운 객체 "ABCDE"는 어퍼케이스에 의해 만들어진 것입니다. 이것을, k에 대입하면 어떻게 될까요?

 

 

 요래 그려질 겁니다. 중요한 것은 k와 li[0]은 같은 객체를 가리키고 있지만, 서로 별개의 공간에 있습니다. k.toUpperCase()를 k에 넣어도, li[0]에는 영향을 끼치지 않습니다. 그리고 나서 2번째로 8번째 줄에 있는 루프를 돌 때, k는 "bcdef"를 가리킬 겁니다.

 

 

 그러면 "ABCDE"는 가비지가 되어서, gc의 수거 대상이 될 겁니다.