본문 바로가기
Java

[Java] String 비교 시 ==이 아닌 equals()를 사용해야 하는 이유

by clolee 2025. 3. 18.

String 비교 시 ==이 아닌 equals()를 사용해야 하는 이유

Java의 String 메모리 공간과 크기

  1. Heap 영역과 String Pool
    • Java에서 String 객체는 Heap 메모리에 저장됩니다.
    • String 리터럴("hello")은 String Pool이라는 특별한 공간에 저장되며, 동일한 값의 String이 있으면 재사용됩니다.
    • new String("hello")는 Heap에 새로운 객체를 생성하므로, String Pool과 다르게 관리됩니다.
  2. 메모리 크기
    • Java의 String은 내부적으로 char[] 배열을 사용하며, 각 char는 2바이트(16비트) 크기의 UTF-16을 사용합니다.
    • 예를 들어 "Hello"는 5 * 2 = 10바이트를 차지합니다.
    • Java 9부터는 byte[] 기반의 String 압축이 도입되어, Latin-1(1바이트)과 UTF-16(2바이트)로 다르게 저장될 수 있습니다.

==이 아닌 equals()를 사용해야 하는 이유

1. ==는 참조(메모리 주소)를 비교

 

  • == 연산자는 두 개의 참조 변수가 같은 객체를 가리키는지 확인할 때 사용합니다.
  • 하지만 String은 String Pool을 활용하기 때문에, new String("hello")처럼 new 키워드를 사용하면 다른 메모리 주소를 가지게 됩니다.

1) String 리터럴

  • "hello"는 컴파일 타임에 String Pool에 저장됩니다.
  • s1과 s2 모두 동일한 "hello" 객체를 가리키므로 s1 == s2는 true가 됩니다.

  
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true ✅ (같은 String Pool 객체 참조)

 

2) new String()을 사용한 경우 (Heap 영역 할당)

  • new String("hello")를 사용하면 새로운 String 객체가 Heap 메모리에 생성되므로 다른 주소를 참조합니다.

  
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false ❌ (Heap의 서로 다른 객체)
System.out.println(s1.equals(s2)); // true ✅ (값 비교)

 

3) .intern()을 사용한 경우

  • intern()을 호출하면 String Pool에 있는 객체를 반환합니다.
  • 따라서, new String("hello").intern()을 사용하면 String Pool의 "hello"와 같은 객체를 참조하게 됩니다.

  
String s1 = new String("hello");
String s2 = s1.intern(); // String Pool의 "hello" 반환
String s3 = "hello";
System.out.println(s2 == s3); // true

 

4) compile-time이 아닌 run-time에 생성된 문자열

  • 컴파일 타임에 결정된 "hello"는 String Pool에 저장되지만, + 연산자를 통한 문자열 연결은 런타임에 새로운 객체를 생성할 수 있습니다.

  
String s1 = "hello";
String s2 = "hel" + "lo"; // 컴파일 타임에 "hello"로 변환됨 (같은 객체)
System.out.println(s1 == s2); // true ✅
String s3 = "hel";
String s4 = s3 + "lo"; // 런타임에 새로운 객체 생성 (Heap 메모리)
System.out.println(s1 == s4); // false ❌

 

해결 방법: .intern() 사용


  
String s3 = "hel";
String s4 = (s3 + "lo").intern(); // String Pool에 저장
System.out.println(s1 == s4); // true ✅

2. equals()는 값 자체를 비교

  • equals() 메서드는 String 내부의 **문자열 내용(값)**을 비교합니다.
  • 따라서, new String("hello")로 생성된 객체도 같은 문자열인지 확인할 때 사용해야 합니다.

  
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false ❌ (주소값 비교)
System.out.println(s1.equals(s2)); // true ✅ (문자열 값 비교)

3. String이 String Pool을 가리킬 때 한 문자열 값을 변경하면?

📌 String이 immutable(불변)한 이유

  • String 객체는 한 번 생성되면 변경할 수 없는 불변 객체(immutable object) 입니다.
  • 즉, String의 값을 바꾸는 것이 아니라 새로운 문자열 객체를 생성합니다.

예제 1: String Pool을 가리킬 때 변경


  
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true (같은 String Pool 객체)
s1 = "world"; // 새로운 String 객체 생성 (기존 "hello"는 유지됨)
System.out.println(s1); // world
System.out.println(s2); // hello
System.out.println(s1 == s2); // false (s1이 새로운 객체를 가리킴)
  • "hello"는 String Pool에 남아 있고, s1은 "world"라는 새로운 객체를 Heap에 만들고 참조를 변경하게 됩니다.
  • 하지만, s2는 여전히 "hello"를 가리킵니다. 기존 Pool의 값이 변경되지 않음.

참고)

예제 2: StringBuffer나 StringBuilder를 사용한 경우

String은 불변(immutable)이므로 변경이 불가능하지만, StringBuffer와 StringBuilder는 가변(mutable) 객체이므로 변경 가능합니다.


  
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
System.out.println(sb.toString()); // hello world
  • StringBuilder는 같은 객체 내에서 변경이 가능하기 때문에, 새로운 객체를 만들지 않습니다.

4. if문과 switch문에서의 == vs equals()

1) if문에서 == vs equals()

  • if 조건문에서 String을 비교할 때 ==를 사용하면 주소 비교가 이루어지므로, 값 비교를 위해 equals()를 사용해야 합니다.

  
String input = new String("yes");
if (input == "yes") { // false (주소 비교)
System.out.println("Matched with ==");
}
if (input.equals("yes")) { // true (값 비교)
System.out.println("Matched with equals()");
}

 

2) switch문에서 String 비교

  • Java 7부터 switch에서 String을 사용할 수 있습니다.
  • switch는 내부적으로 String을 hashCode() 값으로 변환하여 비교하므로, ==이 아니라 문자열 값을 비교합니다.

  
String command = "start";
switch (command) {
case "start":
System.out.println("Starting...");
break;
case "stop":
System.out.println("Stopping...");
break;
default:
System.out.println("Unknown command");
}
  • 위 예제에서 "start".equals(command)와 같은 방식으로 비교가 수행됩니다.
  • null을 switch에 넣으면 NullPointerException이 발생할 수 있으므로 주의해야 합니다.

결론

  • String은 Heap 메모리에 저장되며, 리터럴("text")은 String Pool에서 관리됩니다.
  • ==는 객체의 참조(주소)를 비교하지만, equals()는 문자열 값 자체를 비교하므로 String 비교 시에는 equals()를 사용해야 합니다.
  • if 문에서는 == 대신 equals()를 사용해야 하고, switch 문에서는 내부적으로 equals()를 기반으로 비교하므로 그대로 사용할 수 있습니다.

댓글