기타/자바의 신

(자바)자바의 신 day6 - Java String 완벽 정리: intern(), 문자열 덧셈, 최적화

불광동 물주먹 2025. 6. 29. 21:57

Java String 완벽 정리: intern(), 문자열 덧셈, 최적화까지

Java에서 String은 가장 자주 사용되면서도 성능에 큰 영향을 줄 수 있는 객체입니다. 이번 글에서는 String의 중요한 개념들과 실제 개발 시 주의할 점을 디테일하게 정리해보겠습니다.


🔹 1. String은 불변(Immutable) 객체다

  • String 객체는 생성 이후 변경이 불가능합니다.
  • 새로운 문자열 조작이 발생할 때마다 새로운 객체가 생성됩니다.
String a = "hello";
a += " world"; // "hello world"라는 새로운 String 객체가 생성됨

🔹 2. 문자열 리터럴은 String Constant Pool에 저장된다

  • "hello" 와 같은 리터럴은 JVM의 String Constant Pool이라는 특별한 메모리 공간에 저장됨
  • 동일한 리터럴을 여러 번 사용하더라도 같은 주소 참조를 사용하여 메모리 절약
String a = "test";
String b = "test";
System.out.println(a == b); // true

🔹 3. new String()은 heap에 객체를 생성한다

String a = new String("hello");
String b = "hello";

System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
  • a == b는 false → 서로 다른 객체 참조
  • .equals()는 값이 같으므로 true

🔹 4. intern() 메소드의 개념과 원리

String.intern()은 다음과 같은 역할을 합니다:


 

동작 조건 결과
상수 풀에 동일한 문자열 존재 O 그 참조를 반환
상수 풀에 동일한 문자열 존재 X 현재 문자열을 상수 풀에 추가 후 그 참조 반환
 

💡 도식

String a = new String("hello");
String b = "hello";

a        → heap: "hello"
b        → constant pool: "hello"
a.intern() → constant pool의 "hello" 반환

📌 예제

String a = new String("test");
String b = "test";

System.out.println(a == b); // false
System.out.println(a.intern() == b); // true

⚠️ 5. intern() 사용 시 주의점

  • 너무 많은 문자열을 intern하면 메모리 (Metaspace) 부담 증가 → OutOfMemoryError 가능
  • intern()은 비용이 비싼 연산이므로 빈번하게 사용하지 말 것
  • == 비교를 위해 남용하는 건 권장되지 않음 → equals()가 안전

🔹 6. 문자열 + 연산 (+)의 컴파일 동작 방식

✅ 일반 문자열 덧셈

String name = "Alice";
String greeting = "Hello, " + name;

JDK 5 이상에서는 컴파일 시 다음처럼 자동 변환됨:

String greeting = new StringBuilder()
                    .append("Hello, ")
                    .append(name)
                    .toString();

→ 성능 최적화되어 따로 걱정 안 해도 됨


❌ 반복문 내부에서 +는 비효율적

String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;
}

→ 위 코드는 반복할 때마다 StringBuilder 객체가 매번 생성되고 toString()이 호출됨
→ 결과적으로 성능 저하 및 GC 부하

✅ 권장 방법: StringBuilder 사용

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();

🔍 번외: 바이트 전송을 위한 getBytes()

  • IO 통신 시 문자열을 바이트로 변환할 때 사용
  • 예: 파일 저장, 네트워크 전송 시 필수
String msg = "Hello";
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);

🔍 String Pool에 저장되는 구조는?

JVM은 .class 파일 로딩 시 리터럴들을 Constant Pool 영역에 올려두고,
해당 리터럴이 반복 사용될 경우 기존 객체를 재사용합니다.

→ 이는 메모리 절약과 비교 속도 향상(==)에 도움

String a = "data";
String b = "da" + "ta"; // 컴파일 시점에 "data"로 합쳐짐
System.out.println(a == b); // true

🔚 결론 요약


항목 주의/특징 권장 사항
String 불변 객체 수정 시 새 객체 생성
intern() 상수 풀 참조 반환 자주 사용 시 메모리 문제
+ 연산 1회성은 자동 최적화 반복문에서는 StringBuilder 사용
== vs equals() 참조 vs 값 비교 equals()가 안전
String Pool 리터럴 공유 저장소 동일 문자열 재사용됨