엔지니어로 가는 길

Java String이 불변(immutable)인 이유 본문

프로그래밍/Java

Java String이 불변(immutable)인 이유

탐p슨 2021. 11. 2. 15:48
728x90

아래 두 개의 글에 이어 이번이 세 번째 String 관련 글인 것 같다.

 

Java String으로 보는 동일성과 동등성

Java StringBuilder를 쓰는 이유

 

Java String은 왜 불변일까? 불변이기 때문에 얻을 수 있는 장점에 대해 알아보자.

 

장점 1. 안전

Immutable이기 때문에 안전하다. 몇 개의 예시를 살펴보자.

 

1. 여러 참조 변수가 같은 String 객체를 참조하고 있다고 하더라도 안전하다.

String 객체를 누가 조작할 수가 없기 때문이다.

 

2. String 객체를 이리저리 전달할 때 원본 String 객체가 저장된 주소 자체를 넘겨도 안전하다.

전달받은 곳에서 원본 값을 직접 읽을 순 있으나 조작할 수는 없기 때문이다.

 

장점 2. 성능 및 효율

Immutable은 안전하고, 안전하기 때문에 성능과 효율 면에서 이득을 얻을 수 있다.

 

1. 여러 참조 변수가 같은 String 객체를 참조하고 있다고 하더라도 안전하다

 

덕분에 Java는 힙 영역 안에 String 객체들을 모아 두고(String object pool) 같은 String 값이 여러 참조 변수에 의해 참조될 수 있도록 할 수 있었다. 예를 들어 "java"라는 문자열을 필요로 하는 곳이 100개라면, 100개의 "java" 문자열을 만드는 것이 아니라 하나의 "java" 문자열을 pool 안에 두고 100개가 pool 안에 있는 하나의 "java" 문자열을 참조하도록 하여 성능과 메모리 효율면에서 이득을 얻었다.

 

https://itzone.com.vn/en/article/what-is-string-pool/

 

만약 String이 mutable이라면? 100개의 참조 변수 중 하나의 참조 변수가 "java"를 "JAVA"로 바꿀 경우 다른 99개의 참조 변수 역시 "JAVA"를 바라보게 된다. String이 immutable이 아니었다면 내부적으로 pool을 두는 방식이 불가능했을 것이다.

 

2. String 객체를 이리저리 전달할 때 원본 String 객체가 저장된 주소 자체를 넘겨도 안전하다

 

만약 String이 mutable 했다면 A에서 B로 String을 전달할 때 String을 전달할 때 B가 String을 수정할 수 없도록 하기 위해 String을 복사한 뒤 복사된 String을 B에게 전달했을 것이다. 하지만 immutable이기 때문에 원본 Stirng의 주소를 넘길 수 있고, 전달할 때마다 String이 복사되는 것이 아니기 때문에 성능에서 이득을 얻을 수 있다.

 

물론 B가 원본을 조작해주기를 기대했다면 이렇게 동작할 때 오히려 오버헤드가 발생한다고 볼 수 있을 것이다. 하지만 잘못됐을 경우를 생각해보자. Mutable로 디자인할 경우 안전하지 않다는 크리티컬한 문제가 있는 반면, immutable로 디자인할 경우 기껏해야 새로 객체를 생성하고 이를 리턴하도록 하여 원본이 조작된 효과를 얻어야 한다는 약간의 비효율이 있을 뿐이다.

 

Immutable이기 때문에 개발자가 신경써야 할 부분이 줄어든다는 것 역시 이점으로 볼 수 있다. 다른 곳에서 String을 바꾸지 못하도록 신경 쓰지 않아도 되고, 멀티 쓰레드 환경이라고 하더라도 String에 추가적으로 신경 써줄 필요가 없다.

 

단점

Immutable이라 비효율적인 경우도 있다. String을 연결하는 concatenate 연산에서 immutable이기 때문에 문자열 A에 문자열 B를 더할 때 A에 B를 바로 덧붙이는 것이 아니라 새로운 문자열 객체를 C를 만들고 거기에 A와 B를 더한 결과를 담아야 한다. 다시 말해, 추가적으로 String 객체가 생성되고, A와 B의 값을 모두 복사하는 과정이 이루어진다.

 

 

이런 비효율을 막기 위해서는 StringBuilder 또는 StringBuffer를 이용하면 된다. Single statement인 경우 StringBuilder를 사용하지 않고 그냥 '+' 연산자를 통해 concatenate를 수행하도록 코드를 짰더라도 Java compiler가 알아서 최적화를 진행하여 실제로는 StringBuilder를 이용하여 concatenate를 수행하도록 변환해준다고 한다.

 

하지만 for문 안에서 concatenate가 진행되는 경우 compiler에 의한 최적화가 이루어지지 않으므로 꼭 명시적으로 StringBuilder를 이용하여 concatenate를 해주는 게 좋다.

 

하나 궁금한 점은 왜 애초에 '+'를 통한 concatenate 연산 자체를 StringBuilder를 이용하도록 내부적으로 구현하지 않았을까 하는 점이다. 현재 디폴트인 '새로운 String 객체를 만들어 concatenate'가 StringBuilder를 이용해 concatenate 하는 것보다 쓸모 있을 때가 있을까? 아니면 StringBuilder를 매번 만드는 게 더 큰 오버헤드라고 본 것일까? 음... 어쩌면 StringBuilder는 thread safe하지 않고, StringBuffer는 thread safe 하지만 비효율적이라 디폴트 동작은 그냥 새로운 String 객체를 만들도록 둔 것일지도 모르겠다.

 

Reference

https://www.javatpoint.com/why-string-is-immutable-or-final-in-java

https://stackoverflow.com/questions/21375659/is-new-string-immutable-as-well

https://stackoverflow.com/questions/8798403/string-is-immutable-what-exactly-is-the-meaning

https://stackoverflow.com/questions/279507/what-is-meant-by-immutable

728x90
Comments