Java시작과 문법

String 기초

`String`이 불변 객체인 이유와 그 결과로 `==`가 아닌 `equals()`를 써야 하는 이유, 반복 연결에서 `StringBuilder`가 필요한 시점을 정리합니다.

마지막 수정 2026년 3월 27일

기본 패턴

java
String name = "Kim";

// 값 비교는 반드시 equals()
String a = new String("hello");
String b = new String("hello");
System.out.println(a.equals(b)); // true
System.out.println(a == b);      // false — 참조가 다름

// 자주 쓰는 메서드
System.out.println(name.length());           // 3
System.out.println(name.toUpperCase());      // "KIM"
System.out.println("  hello  ".strip());     // "hello"
System.out.println(name.contains("im"));     // true

설명

String이 불변인 이유와 그 결과

Java의 String불변(immutable) 객체입니다. 한 번 생성된 String의 내용은 변경할 수 없습니다. s = s + "x" 처럼 보이는 수정 코드는 실제로는 새 String 객체를 만들어 참조를 교체합니다.

불변으로 설계된 이유는 여러 스레드가 같은 문자열을 안전하게 공유할 수 있고, String Pool(인터닝)을 통한 메모리 최적화가 가능하기 때문입니다. 단점은 수정할 때마다 새 객체가 생겨 메모리 압박이 생길 수 있다는 것입니다.

java
String s = "hello";
s = s + " world"; // s는 새 객체 "hello world"를 가리킴
                  // 원래 "hello" 객체는 GC 대상

// 다른 변수에서 참조했다면 영향 없음
String original = "hello";
String modified = original + " world";
System.out.println(original); // "hello" — 변화 없음

== vs equals() — 참조 비교 vs 값 비교

==는 두 참조가 같은 객체를 가리키는지 확인합니다. 값이 같아도 객체가 다르면 false입니다. equals()내용이 같은지 비교합니다.

String Pool 때문에 리터럴 문자열은 ==true일 수 있지만, new String()으로 만든 경우나 런타임에 생성된 문자열은 그렇지 않습니다. 문자열 비교는 항상 equals()를 사용하는 것이 안전합니다.

java
// 리터럴 — String Pool에서 같은 객체 재사용
String a = "hello";
String b = "hello";
System.out.println(a == b);      // true (Pool에서 같은 객체)
System.out.println(a.equals(b)); // true

// new — 항상 새 객체
String c = new String("hello");
System.out.println(a == c);      // false (다른 객체)
System.out.println(a.equals(c)); // true (값은 같음)

// 런타임 생성 — Pool 보장 없음
String d = "hel" + "lo";        // 컴파일 타임 상수 — Pool 사용
String e = "hel";
String f = e + "lo";            // 런타임 연결 — 새 객체
System.out.println(a == f);     // false

StringBuilder — 반복 연결의 비용 문제

String이 불변이므로 +로 반복 연결하면 매번 새 객체가 생성됩니다. 루프 안에서 n번 연결하면 중간 객체가 O(n)개 생깁니다. StringBuilder는 내부 가변 버퍼에 문자를 누적하므로 최종 결과 객체 하나만 생성됩니다.

단순 + 연결 몇 번은 컴파일러가 StringBuilder로 자동 최적화하지만, 루프 안에서는 최적화가 되지 않으므로 직접 써야 합니다.

java
// ❌ 루프 안 + 연결 — 매번 새 String 생성
String result = "";
for (String word : words) {
    result += word + " "; // 루프마다 새 객체 2개
}

// ✅ StringBuilder — 버퍼에 누적, 마지막에 한 번 변환
StringBuilder sb = new StringBuilder();
for (String word : words) {
    sb.append(word).append(" ");
}
String result = sb.toString();

빠른 정리

상황선택
문자열 값 비교equals() (== 금지)
대소문자 무시 비교equalsIgnoreCase()
반복 문자열 연결 (루프)StringBuilder
단순 연결 몇 번+ (컴파일러 최적화)
앞뒤 공백 제거strip() (Unicode 지원)
부분 문자열substring(start, end)
문자열 → 숫자Integer.parseInt(s)

주의할 점

null 체크 없이 equals()를 호출하면 NullPointerException이 발생합니다. 상수나 리터럴을 앞에 두거나 Objects.equals()를 사용하세요.

java
String input = null;

// ❌ null인 변수에 equals() 호출 — NullPointerException
if (input.equals("hello")) { ... }

// ✅ 리터럴을 앞에 두면 NPE 없음
if ("hello".equals(input)) { ... } // input이 null이면 그냥 false

// ✅ Objects.equals() — 양쪽 null 안전
if (Objects.equals(input, "hello")) { ... }

참고 링크

2 sources