자바를 처음 배울 때 가장 헷갈리는 부분 중 하나가 바로 문자열 비교다.
“abc” == “abc”가 true인데, 왜 “abc” == new String(“abc”)는 false일까?
이런 질문을 해본 적이 있다면 이 글을 끝까지 읽어보자.
처음 자바를 배울 때 이런 코드를 써봤을 것이다.
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); // true
String str3 = new String("hello");
System.out.println(str1 == str3); // false <- 여기서 멘붕
“똑같은 문자열인데 왜 false가 나오지?”
많은 초보자들이 이 순간에서 멘붕한다. 나도 그랬다.
== 연산자는 참조(주소)를 비교한다.
즉, 두 변수가 같은 메모리 주소를 가리키고 있는지 확인한다.
String a = "test";
String b = "test";
String c = new String("test");
System.out.println(a == b); // true - 같은 주소
System.out.println(a == c); // false - 다른 주소
equals() 메서드는 실제 값을 비교한다.
문자열의 내용이 같은지 확인한다.
String a = "test";
String b = new String("test");
System.out.println(a.equals(b)); // true - 내용이 같음
왜 “hello” == “hello”는 true일까?
이건 String Pool 때문이다.
JVM은 문자열 리터럴을 특별한 메모리 영역인 String Pool에 저장한다.
같은 내용의 문자열 리터럴은 하나의 인스턴스만 생성하고 재사용한다.
String str1 = "hello"; // String Pool에 "hello" 생성
String str2 = "hello"; // 기존 "hello" 재사용 (같은 주소)
String str3 = new String("hello"); // 새로운 객체 생성 (다른 주소)
String Pool: Heap:
┌─────────────┐ ┌─────────────┐
│ "hello" │ │ "hello" │
│ (주소: 100) │ │ (주소: 200) │
└─────────────┘ └─────────────┘
↑ ↑
str1, str2 str3
str1과 str2는 같은 주소(100)를 가리키지만, str3는 다른 주소(200)를 가리킨다.
public class StringComparison {
public static void main(String[] args) {
// 리터럴 방식
String literal1 = "java";
String literal2 = "java";
System.out.println("리터럴 비교: " + (literal1 == literal2)); // true
// new 연산자 방식
String new1 = new String("java");
String new2 = new String("java");
System.out.println("new 비교: " + (new1 == new2)); // false
// 리터럴 vs new
System.out.println("리터럴 vs new: " + (literal1 == new1)); // false
// equals() 비교
System.out.println("equals 비교: " + literal1.equals(new1)); // true
}
}
if (str == null)
String str = null;
if (str == null) {
System.out.println("문자열이 null입니다");
}
String input = "admin";
if ("admin".equals(input)) { // null 안전
System.out.println("관리자입니다");
}
// 위험한 방법
String str = null;
if (str.equals("test")) { // NullPointerException 발생 가능
// ...
}
// 안전한 방법 1
if ("test".equals(str)) { // null 안전
// ...
}
// 안전한 방법 2 (Java 7+)
if (Objects.equals(str, "test")) { // 완전히 안전
// ...
}
// 잘못된 방법
if (input.equals("YES")) { // "yes", "Yes" 등은 false
// 올바른 방법
if ("YES".equalsIgnoreCase(input)) { // 모든 경우 처리
String userInput = " hello ";
if (userInput.equals("hello")) { // false
// 올바른 방법
if (userInput.trim().equals("hello")) { // true
StringBuilder sb = new StringBuilder("hello");
if (sb.equals("hello")) { // false - 타입이 다름
// 올바른 방법
if (sb.toString().equals("hello")) { // true
== 연산자가 equals()보다 빠르다.
하지만 올바른 결과를 위해 equals()를 써야 한다.
// 빠르지만 위험
if (str1 == str2) { // O(1) - 주소 비교만
// 느리지만 안전
if (str1.equals(str2)) { // O(n) - 문자 하나씩 비교
==와 equals()의 차이를 이해하는 건 자바의 기본이다.
String Pool의 존재를 알고, 언제 어떤 비교 방법을 써야 하는지 아는 것이 중요하다.
기억하자:
이제 “abc” == new String(“abc”)가 false인 이유를 완전히 이해했을 것이다.
같은 내용이지만 다른 객체이기 때문이다.