Java String

Seoyeon Kim·2023년 3월 6일
0

String 객체 생성하기

객체 생성 방법 1

new 키워드를 사용하는 방법

String 참조 변수명 = new String("문자열")

// 예시
String str = new String("안녕");

객체 생성 방법 2

문자열 리터럴을 입력하는 방법

String 참조 변수명 = "문자열"

// 예시
String str = "안녕";

첫 번째 방법을 사용하든, 두 번째 방법을 사용하든 메모리에 저장되는 방식은 동일하다. String은 참조 자료형이므로 실제 데이터인 String 객체는 힙 메모리에 위치하고, 참조 변수는 힙 메모리의 실제 객체 위치를 가리키게 될 것이다. 하지만 이 2가지 방법 사이에는 결정적인 차이가 1개 있다.

String 클래스의 2가지 특징

String은 워낙 자주 사용되는 클래스이다 보니 다른 클래스에는 없는 2개의 특징이 있다.

특징 1 : 객체 안의 값을 변경하면 새로운 객체를 생성

한 번 정의된 문자열은 변경할 수 없다. 만일 문자열의 내용을 변경하면 자바 가상 머신은 기존의 문자열을 수정하는 것이 아니라 새로운 문자열을 포함하고 있는 객체를 생성해 사용하고 기존의 객체는 버린다.

String str1 = new String("Hello");
String str2 = str1;
str1 = "Hi";
System.out.println(str1); // Hi
System.out.println(str2); // Hello

먼저 코드를 String str1 = new String("Hello"); 과 같은 형태로 작성하면 메모리의 클래스 영역에는 String 클래스의 바이트 코드가 로딩되고, 스택에는 참조 변수 str1의 공간이 생긴다. 실제 데이터인 "Hello"는 힙 메모리에 생성되며, 생성된 실제 데이터의 위치값이 스택 메모리의 str1 공간에 저장될 것이다.

이 상황에서 String str2 = str1; , 즉 참조 자료형의 값을 복사하면 스택 메모리의 값이 복사되므로 str1과 str2는 이제 모두 동일한 객체를 가리키고 있을 것이다.

이제 str1 = "Hi"; 와 같이 수정하면 자바 가상 머신은 기존의 문자열을 수정하는 것이 아닌 "Hi"라는 문자열을 포함하고 있는 새로운 String 객체를 생성하고 이 위치를 str1의 공간에 저장한다. 이는 일반적으로 참조 자료형에서 2개의 참조 변수가 1개의 객체를 가리킬 때 하나의 참조 변수에 접근해 객체의 값을 변경하면, 다른 참조 변수가 가리키는 값도 함께 변하는 것과는 구분되는 특징이다.

특징 2 : 리터럴을 바로 입력한 데이터는 문자열이 같을 때 하나의 객체를 공유

문자열 리터럴을 바로 입력해 객체를 생성할 때 같은 문자열끼리 객체를 공유한다. 이는 메모리의 효율성 때문이다.

String str1 = new String("Hello");
String str2 = "Hello";
String str3 = "Hello";
String str4 = new String("Hello");

모두 동일한 문자열을 포함하고 있으며, 첫 번째와 네 번째는 new 키워드, 나머지 2개는 문자열 리터럴을 사용했다.

String str1 = new String("Hello"); 이 실행되면 힙 메모리에는 "Hello"라는 객체 하나가 생성될 것이다. 두 번째는 문자열 리터럴로 입력했는데, 이때는 힙 메모리에 이미 "Hello"가 있어도 새롭게 "Hello"라는 객체를 추가한다. 세 번째도 문자열 리터럴로 생성했는데, 이때 이미 앞에서 문자열 리터럴로 생성한 "Hello"라는 객체가 있으므로 새롭게 객체를 생성하는 것이 아니라 기존에 있는 "Hello" 객체를 공유한다. 마지막에는 다시 new로 객체를 생성했으며, 이때는 새롭게 객체를 생성한다.

정리하면 new로 생성할 때는 동일한 문자열 객체가 힙 메모리에 있든, 없든 무조건 새롭게 객체를 생성한다. 문자열 리터럴로 생성할 때는 힙 메모리에 리터럴로 생성된 동일 문자열을 포함하고 있는 객체가 있으면 그 객체를 공유한다.

// 스택 메모리값 비교
System.out.println(str1 == str2); // false
System.out.println(str2 == str3); // true
System.out.println(str3 == str4); // false
System.out.println(str4 == str1); // false

참고 자바 완전 정복 189 page ~

String이 Immutable한 이유

✔️ Why String is Immutable in Java?
✔️ Why String is Immutable or final in Java? - 5 Reasons
✔️ Why String is Immutable or final in Java? - 5 Reasons 번역글

  1. String Pool
    가장 많이 사용하는 데이터 타입인 String의 최적화를 위해 String 객체를 공유함으로써 생성되는 String 객체를 줄이고자 하였다. 그렇다면 같은 객체를 참조하는 변수가 서로의 영향을 받지 않도록 신경써야 했는데 String을 immutable하게 만드는 것으로 공유가 가능하게 했다.
  2. Security
  3. Use of String in Class Loading Mechanism
  4. Multithreading Benefits
  5. Optimization and Performance

주의할 점

String을 리터럴로 생성하면 해당 String 값은 Heap 영역 내 String Constant Pool 에 저장되어 재사용되지만, new 키워드로 생성하면 같은 내용이라도 여러 개의 객체가 각각 Heap 영역을 차지하도록 되어있다.

따라서 String Pool에서 객체를 가져올 수 없는 new String()이라는 생성자를 사용하지 않도록 주의해야 한다.

StringBuilder vs StringBuffer

String vs StringBuilder & StringBuffer

String : Immutable
StringBuilder & StringBuffer : Mutable

StringBuilder와 StringBuffer는 AbstractStringBuilder라는 추상 클래스를 상속받아 구현되어 있다.

StringBuilder와 StringBuffer에 문자열을 추가하면 추가할 문자열의 크기만큼 문자열을 저장하는 배열의 공간을 늘려주고 해당 공간에 추가할 문자열을 넣어준다. 따라서 값이 변경되더라도 같은 주소 공간을 참조하며 값은 변경되는 가변성을 띠게 된다.

StringBuffer

StringBuffer는 메서드에서 synchronized 키워드를 사용한다.

StringBuilder 클래스 주석에서 '동기화가 필요할 경우 StringBuffer을 추천한다' 는 문구를 확인할 수 있다.

StringBuilder

StringBuilder는 동기화를 지원하지 않기 때문에 StringBuffer보다 처리 속도가 빠르다.

따라서 단일 스레드 환경에서 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuilder를 사용하는 것이 좋다.

0개의 댓글