Skip to main content

Command Palette

Search for a command to run...

Java에서 String이 불변(Immutable)인 이유

Updated
3 min read

Java에서 String은 **불변 객체(Immutable Object)**입니다.

즉, 한 번 생성된 String 객체는 내부 상태를 변경할 수 없습니다.

Java의 창시자 제임스 고슬링(James Gosling)은 다음과 같이 말했습니다.

“가능하면 언제나 불변 객체를 사용하라.”

그 이유로는 캐싱, 보안, 재사용성, 동기화, 성능 향상 등을 언급했죠.

이번 포스팅에서는 왜 String이 불변으로 설계되었는지, 그리고 리터럴과 생성자의 차이, intern() 메서드까지 함께 살펴보겠습니다.


불변 객체(Immutable Object)란?

불변 객체는 한 번 생성되면 내부 상태를 절대 변경할 수 없는 객체입니다.

  • 생성 이후 필드 값 변경 불가

  • 다른 참조로 대체 불가

String은 대표적인 불변 객체입니다.


String이 불변인 이유

1. 문자열 상수 풀(String Pool) 활용

Java는 **문자열 리터럴을 상수 풀(String Pool)**에 저장하고 공유합니다.


String s1 = "Hello";
String s2 = "Hello";

System.out.println(s1 == s2); // true
  • "Hello"는 상수 풀에 한 번만 저장됨

  • s1, s2는 같은 객체를 참조 → 메모리 절약 + GC 부담 감소

만약 **String이 가변(mutable)**이라면, 하나의 참조가 값을 바꾸면 다른 참조에도 영향을 주기 때문에 풀 공유가 불가능했을 것


2. 보안(Security)

String은 민감한 데이터를 담습니다.

예: 사용자 이름, 비밀번호, DB 연결 정보 등


void criticalMethod(String userName) {
    if (!isAlphaNumeric(userName)) {
        throw new SecurityException();
    }

    connection.executeUpdate("UPDATE users SET name='" + userName + "'");
}

만약 String이 가변이라면, 외부 코드가 userName을 조작해서 SQL Injection 등의 보안 문제를 일으킬 수 있습니다.

불변성 덕분에 이런 보안 문제를 방지할 수 있습니다.


3. 스레드 안전(Thread-Safety)

불변 객체는 상태가 바뀌지 않기 때문에,

동기화 없이 여러 스레드에서 안전하게 공유할 수 있습니다.

→ 복잡한 락(lock) 없이도 안전하게 사용할 수 있어 멀티스레드 환경에 유리합니다.


4. 해시코드 캐싱

StringHashMap, HashSet 등의 해시 기반 컬렉션의 키로 자주 사용됩니다.

  • StringhashCode()를 오버라이드하여 한 번 계산한 값은 캐시

  • 값이 바뀌지 않으니 항상 동일한 해시값을 보장

만약 String이 변경된다면 → 해시값이 바뀌고 → 컬렉션에서 찾을 수 없는 상황 발생


5. 성능 최적화

  • 상수 풀 재사용

  • 해시코드 캐싱

  • 동기화 생략 가능

이 모든 요소들이 모여, Java 애플리케이션 전반의 성능 향상에 기여합니다.


리터럴 vs 생성자 방식

String 객체는 두 가지 방식으로 생성할 수 있습니다:

1. 리터럴 방식


String str1 = "hello";
String str2 = "hello";

System.out.println(str1 == str2); // true
  • "hello"는 상수 풀에 저장됨

  • str1과 str2는 같은 객체를 참조

✅ 메모리 절약

✅ 성능 향상


2. 생성자 방식 (new 키워드)


String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str3 == str4); // false
  • new를 사용하면 항상 Heap에 새 객체 생성

  • 내용은 같지만 객체는 다름


System.out.println(str3.equals(str4)); // true

리터럴 방식보다 메모리 낭비 가능성 높음

→ 특별한 이유가 없다면 new는 지양하는 것이 좋습니다.


intern() 메서드란?


String s1 = new String("hello");
String s2 = s1.intern();
  • s1은 Heap에 있는 새 객체

  • s2"hello"라는 상수 풀의 문자열을 참조

즉, intern()은:

힙에 있는 문자열을 상수 풀의 동일한 객체로 참조 변경하는 메서드입니다.


예제


String a = "hello";                 // 상수 풀
String b = new String("hello");    // Heap
String c = b.intern();             // 상수 풀 참조

System.out.println(a == b); // false
System.out.println(a == c); // true
  • a는 상수 풀

  • b는 Heap 객체

  • cintern()을 통해 상수 풀 참조 → a == ctrue

참고 자료 :https://www.baeldung.com/java-string-immutable

More from this blog

낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)

락(Lock)의 필요성 현대 애플리케이션은 대부분 동시성(Concurrency) 문제에 직면합니다. 여러 사용자나 프로세스가 동시에 같은 데이터를 수정하거나, 은행 계좌 이체와 같은 중요한 트랙잭션을 동시에 실행하는 경우가 있습니다. 이때 동시 접근을 적절히 제어하지 않으면, 데이터 불일치, 중복 처리, 시스템 오류까지 발생할 수 있습니다. 이를 방지하기 위해 락(Lock) 개념이 도입되었습니다. 락은 여러 프로세스나 스레드가 동시에 동일한 자...

Aug 22, 20253 min read

Java 프로그램이 실행되는 흐름

자바 프로그램은 단순히 .java 파일을 실행하는 것이 아니라, 컴파일 → 로드 → 실행 이라는 여러 단계를 거쳐 최종적으로 CPU 가 이해할 수 있는 기계어로 변환됩니다. 이 과정에서 JDK, JVM, JRE가 각각 어떤 역할을 하는지 이해하는 것이 중요합니다. 1. 소스 코드 작성과 컴파일 개발자는 자바 소스 파일(.java)을 작성 JDK 에 포함된 javac (Java Compiler)가 소스 코드를 컴파일 하여 JVM이 이해할 수...

Aug 21, 20252 min read

Java Collection Framework

컬렉션 프레임워크(Collection Framework)란? 자바에서 컬렉션 프레임워크란 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스와 인터페이스 집합입니다. 즉, 데이터를 저장하는 자료구조과 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현한 것입니다. 모든 컬렉션 프레임워크는 자바의 인터페이스를 기반으로 구현됩니다. JFC(Java Collection Framework)의 도입 배경 JCF 이전에는 ...

Aug 14, 20254 min read

운영체제의 발전: 단일 프로세스에서 멀티코어까지

운영체제의 발전은 ‘CPU를 얼마나 효율적으로 활용하느냐’의 역사라고 볼 수 있습니다. 초기에는 한 번에 하나의 프로그램만 실행했지만, 점차 CPU 사용률과 사용자 경험을 높이기 위해 멀티프로그래밍, 멀티태스킹, 멀티프로세스, 멀티스레딩, 그리고 멀티코어 구조가 발전했습니다. 들어가기에 앞서 프로세스와 스레드에 대해 간단히 설명하겠습니다. 프로세스(Process) 실행 중인 프로그램으로 OS로부터 독립된 주소 공간과 자원을 할당 받음 각 ...

Aug 13, 20252 min read

Keep-Alive (HTTP Keep-Alive와 TCP Keep-Alive)

Keep-Alive는 연결(Connection)을 계속 유지하기 위한 메커니즘입니다. 네트워크에서 어떤 연결을 만들고, 요청/응답을 주고받은 후 바로 끊지 않고 일정 시간 동안 유지하면, 그 시간 안에 들어오는 추가 요청은 이미 열린 연결을 재사용할 수 있습니다. 이를 통해 매번 연결을 새로 맺는데 필요한 오버헤드(3-Way Handshake)를 줄일 수 있고, 불필요한 지연(latency)를 감소시킬 수 있습니다. 하지만 연결을 너무 오래 유...

Aug 12, 20252 min read

gaeng

22 posts