Java에서 String 객체는 불변입니다 — 한 번 생성되면 그 내용을 절대 변경할 수 없습니다. String을 수정하는 것처럼 보이는 모든 연산은 실제로는 새로운 String을 생성합니다. 효율적인 반복 수정을 위해서는 대신 StringBuilder를 사용합니다.
String은 불변이다
;
s.concat();
System.out.println(s);
s = s + ;
concat, toUpperCase, replace, substring 같은 메서드는 모두 새로운 String 객체를 반환합니다 — 원본은 절대 수정되지 않습니다.
✓ 스레드 안전성 — 불변 객체는 스레드 간 공유에 안전함 (잠금 불필요)
✓ String 풀 — 동일한 문자열 리터럴을 공유/재사용 가능 (메모리 절약)
✓ 해싱/키 — HashMap 키로 안전 (hashCode 를 캐싱 가능, 절대 변하지 않음)
✓ 보안 — 파일명, URL, 자격 증명으로 쓰이는 문자열을 보안 검사 후
변경할 수 없음
불변성은 String을 본질적으로 스레드 안전하게 만들고, JVM이 String 풀을 통해 리터럴을 인터닝(공유)할 수 있게 하며, 맵 키로 신뢰할 수 있게 만듭니다(해시가 절대 변하지 않음).
// ❌ 매 반복마다 새로운 문자열 객체를 생성 — O(n²), 낭비적
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 각 += 가 완전히 새로운 문자열을 할당
}
각 +=가 새로운 String을 생성하므로(이전 내용을 모두 복사), 루프에서 문자열을 만드는 것은 2차 시간이 걸리고 엄청난 양의 가비지를 생성합니다.
// ✅ StringBuilder 는 내부 버퍼를 변경 — O(n), 효율적
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // 버퍼를 제자리에서 수정, 새 객체 없음
}
String result = sb.toString(); // 마지막에 한 번만 String 으로 변환
StringBuilder는 가변 문자 시퀀스입니다 — append/insert/delete가 새 객체를 생성하지 않고 내부 버퍼를 수정하여 루프 기반 빌드를 효율적으로 만듭니다. (StringBuffer는 스레드 안전하지만 더 느린 변형입니다.)
String 불변성은 중요한 결과를 가진 Java의 정의적 특징입니다. 스레드 안전성을 제공하고, 메모리를 절약하는 String 풀을 가능하게 하며, String을 맵 키와 보안 컨텍스트에서 안전하게 만듭니다 — 하지만 동시에 순진한 루프 내 문자열 연결이 실제 성능 함정(O(n²), 과도한 가비지)이라는 것을 의미합니다.
반복 수정에 StringBuilder를 사용해야 한다는 것을 아는 것은 불변성을 이해하는 데서 직접 나오는 실무적 필수 사항입니다.
이 조합 — String이 왜 불변인지(안전성, 공유, 해싱)와 언제 StringBuilder로 전환해야 하는지(루프 연결) — 는 흔한 면접 주제이자 자주 등장하는 실무 성능 개선입니다.