들어가며
String literalString = "ABC";
String objectString = new String("ABC");
이번 포스팅은 위 코드의 차이를 설명하기 위해 작성되었다. Java의 메모리 구조를 이해하기 위한 좋은 예제이므로 이미 알고 계신 분들은 가볍게 보시고, 모르는 분들도 쭉 글을 읽고 나면 금방 이해할 수 있다.
String은 무엇인가?
String은 한글로 '문자열'이라고 부른다. 문자열은 '문자 배열'을 줄여 부르는 말이다. 그래서 String == char[] 로 이해하고 char[]를 쉽게 사용하기 위해 만든 객체를 String이라고 생각해도 좋다.
사용방법은 크게 두 가지가 있다. 첫 번째는 리터럴방식으로 쌍따옴표(")사이에 원하는 문자열을 선언한다. 두 번째는 생성자 방식으로 객체 생성자를 호출하듯 new String() 형태로 문자열을 생성한다.
그런데 무언가 이상하다. 리터럴 방식은 int, char, boolean과 같은 기본자료형에 데이터를 할당하는 방식이다. 객체자료형인 String이 리터럴방식을 사용할 수 있는 것은 이상한 일이다. 이 뿐만이 아니다. 두 String 객체를 '+' 연산자를 통해 값을 하나로 연결할 수 있다. 다른 객체자료형은 불가능한 방법이다.
이렇듯 String은 여타 객체와는 다른 특별함이 있다. 여러 특별함 중 이번 주제를 위해 불변객체와 인터닝에 대해서만 자세히 살펴보겠다.
1. 불변객체
String text = "Hello," + " world";
text = text + " java!";
위 코드에서 String 객체는 아래와 같이 총 5개의 String 객체가 생겼다.
1) "Hello,"
2) " world"
3) "Hello, world"
4) " java!"
5) "Hello, world java!"
객체는 주소를 참조기 때문에 하나의 객체에 값만 변할 것 같지만, 불변객체는 생성이 될 때의 값만 가지기 때문에 변경이 있을때마다 새로운 객체를 만든다.
2. 인터닝
String 객체를 리터럴 방식으로 선언하면 String constant pool(스트링 상수 풀)이란 공간에 저장된다. 불변객체의 특징때문에 String 객체는 개발자가 생각하는 것 이상으로 만들어지기 때문에 특정한 공간에 두고 재활용하기 위해 String constant pool이 존재한다. 이런 방식을 Interning이라고 한다. 아래 테스트 코드를 보자.
@Test
public void stringIntern() {
final String literal = "Hello"; // 리터럴
final String object = new String("Hello"); // 객체
final String intern = literal.intern(); // 리터럴의 인턴
Assert.assertTrue(literal.equals(object)); // true
Assert.assertFalse(literal == object); // false
Assert.assertTrue(literal.equals(intern)); // true
Assert.assertTrue(literal == intern); // true
}
리터럴과 인턴된 객체의 주소값 비교(==)를 했는데도 true가 나온다. 동등성을 넘어 동일성까지 갖는다는 뜻이다. 이래서 String은 생성자 방식 보다 리터럴 방식을 사용해야 성능측면에서 좋은 효율을 낼 수 있다.
마치며
정리해보자. String 객체를 선언하는 방법 두 가지를 비교해보았다.
먼저 문자열 리터럴 방식은 String constant pool에 사용된 String 객체의 값을 저장한다. 이후 String constant pool에 존재하는 값을 재활용하면서 같은 값을 가진 String 객체가 중복해서 생성되는 것을 방지한다.
다음은 생성자를 사용하여 선언하는 방식으로 보통의 객체들 처럼 Heap에 저장된다. 따라서 동일한 값을 가진 String 객체를 생성하더라도 새로운 주소값을 같은 다른 객체로 인식하게 된다. 그래서 같은 값을 가진 String 객체가 여러개 존재할 수 있다.
String은 객체지만 특별한 객체이다. 그렇기 때문에 특별한 방법으로 다루는 것이 좋다. 리터럴 방식을 사용해야 불필요한 객체의 생성을 막을 수 있고, 다른 개발자와 협업하는데 가독성도 좋은 코드를 만들 수 있다. 만약 이런 이유를 알고도 new 키워드를 사용해야 한다면 명확한 이유를 설명할 수 있어야 한다.
참고자료
github.com/bbubbush/java_programming_interview/blob/master/docs/자바%20기본/String%20이용하기.md
www3.ntu.edu.sg/home/ehchua/programming/java/J3d_String.html
'Development > Java' 카테고리의 다른 글
[기술면접준비] 멀티스레드 상황에서 어떤 Map 구현체를 사용해야할까? (0) | 2023.04.05 |
---|---|
JDK8에서 SHA512 암호화/해시화 구현하기 (0) | 2022.04.22 |
JDK8에서 AES 암/복호화 구현하기 (0) | 2022.03.30 |
Collection살펴보기 - Set (0) | 2021.01.14 |
Java 8의 주요 변경 사항과 실무 적용 포인트 (6) | 2020.12.30 |