Java 문자열 비교 마스터하기: “==”, equals(), compareTo() 차이점 및 모범 사례

1. 소개

왜 문자열 비교가 Java에서 중요한가?

Java 프로그래밍에서는 문자열이 다양한 상황에서 사용됩니다. 사용자 이름 확인, 폼 입력 검증, API 응답 확인 모두 문자열 비교가 필요합니다.
이 시점에서 “문자열을 올바르게 비교하는 방법”은 초보자에게 흔히 겪는 난관입니다. 특히 == 연산자와 equals() 메서드의 차이를 이해하지 못하면 예상치 못한 버그가 발생할 수 있습니다.

“==”와 “equals” 차이를 이해하지 못했을 때의 위험

다음 코드를 고려해 보세요:

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

System.out.println(a == b);       // result: false
System.out.println(a.equals(b));  // result: true

많은 사람들이 이 출력에 놀라곤 합니다. 문자열이 동일함에도 ==는 false를 반환하고 equals()는 true를 반환합니다. 이는 Java가 문자열을 레퍼런스 타입으로 취급하고 ==가 레퍼런스 주소를 비교하기 때문입니다.
문자열을 올바르게 비교하는 방법을 이해하면 프로그램의 신뢰성과 가독성에 직접적인 영향을 미칩니다. 제대로 이해하면 버그가 발생하기 전에 예방할 수 있습니다.

이 글에서 배울 내용

이 글은 Java 문자열 비교를 기본부터 실용적인 적용까지 설명합니다. 다음과 같은 질문에 답합니다:

  • ==equals()의 차이는 무엇인가?
  • 대소문자를 무시하고 문자열을 비교하려면 어떻게 해야 하나요?
  • 문자열을 사전식으로 비교하려면 어떻게 해야 하나요?
  • null과 비교할 때 예외를 피하려면 어떻게 해야 하나요?

실제 예제를 통해 올바른 문자열 비교 실천법에 대한 확고한 이해를 얻을 수 있습니다.

2. Java 문자열 기본

문자열은 레퍼런스 타입이다

Java에서 String 타입은 intboolean 같은 원시 타입이 아니라 레퍼런스 타입입니다. String 변수는 실제 문자 자체를 저장하지 않고 힙 메모리에 저장된 객체에 대한 레퍼런스를 저장합니다.
예를 들어:

String a = "hello";
String b = "hello";

Java의 문자열 인터닝 메커니즘 때문에 ab가 동일한 "hello" 리터럴을 가리킬 수 있습니다.

문자열 리터럴과 new String()의 차이

Java는 반복되는 문자열 리터럴을 동일한 레퍼런스를 재사용함으로써 최적화합니다:

String s1 = "apple";
String s2 = "apple";
System.out.println(s1 == s2); // true (same literal, interned)

하지만 new를 사용하면 항상 새로운 객체가 생성됩니다:

String s3 = new String("apple");
System.out.println(s1 == s3); // false (different references)
System.out.println(s1.equals(s3)); // true (same content)

따라서 ==레퍼런스를, equals()내용을 확인합니다.

문자열은 불변이다

또 다른 핵심 특징은 String불변(immutable)이라는 점입니다. 한 번 생성된 문자열은 변경할 수 없습니다.

String original = "hello";
original = original + " world";

이 코드는 원본을 수정하는 것이 아니라 새로운 String 객체를 생성합니다.

3. 문자열 비교 메서드

== 로 레퍼런스 비교

==는 객체 레퍼런스를 비교합니다:

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

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

내용 비교에는 사용해서는 안 됩니다.

equals() 로 내용 비교

equals()는 문자열 내용을 비교하는 올바른 방법입니다:

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

System.out.println(a.equals(b)); // true

NullPointerException 방지

String input = null;
System.out.println(input.equals("test")); // Exception!

상수-우선 스타일을 사용합니다:

System.out.println("test".equals(input)); // false, safe

equalsIgnoreCase() 로 대소문자 무시

String a = "Hello";
String b = "hello";

System.out.println(a.equalsIgnoreCase(b)); // true

compareTo() 로 사전식 비교

  • 0 → 동일
  • 음수 → 호출자가 앞에 있음
  • 양수 → 호출자가 뒤에 있음
    String a = "apple";
    String b = "banana";
    
    System.out.println(a.compareTo(b)); // negative
    

4. 실용 예제

사용자 로그인 확인

String inputUsername = "Naohiro";
String registeredUsername = "naohiro";

if (registeredUsername.equalsIgnoreCase(inputUsername)) {
    System.out.println("Login successful");
} else {
    System.out.println("Username does not match");
}

Passwords should always use equals() because case sensitivity is required.

폼 입력 검증

String selectedOption = request.getParameter("plan");

if ("premium".equals(selectedOption)) {
    System.out.println("Premium plan selected.");
} else {
    System.out.println("Other plan selected.");
}

여러 문자열 검사를 이용한 분기 로직

String cmd = args[0];

if ("start".equals(cmd)) {
    startApp();
} else if ("stop".equals(cmd)) {
    stopApp();
} else {
    System.out.println("Invalid command");
}
switch (cmd) {
    case "start":
        startApp();
        break;
    case "stop":
        stopApp();
        break;
    default:
        System.out.println("Unknown command");
}

null 안전하게 처리하기

String keyword = null;

if ("search".equals(keyword)) {
    System.out.println("Searching...");
}

5. 성능 및 최적화

문자열 비교 비용

equals()compareTo()는 내부적으로 문자를 비교합니다. 긴 문자열이나 반복적인 비교에서는 성능에 영향을 줄 수 있습니다.

성능을 위한 String.intern() 사용

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

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

메모리 압박을 피하기 위해 필요할 때만 사용하십시오.

equalsIgnoreCase()의 성능 영향

String input = userInput.toLowerCase();
if ("admin".equals(input)) {
    // fast comparison
}

StringBuilder / StringBuffer 사용

StringBuilder sb = new StringBuilder();
sb.append("user_");
sb.append("123");

String result = sb.toString();

if (result.equals("user_123")) {
    // comparison
}

캐시 또는 맵을 사용해 비교 횟수 줄이기

Map<String, Runnable> commandMap = new HashMap<>();
commandMap.put("start", () -> startApp());
commandMap.put("stop", () -> stopApp());

Runnable action = commandMap.get(inputCommand);
if (action != null) {
    action.run();
}

6. 자주 묻는 질문

Q1. ==equals()의 차이점은 무엇인가요?

A.

  • ==는 레퍼런스를 비교합니다
  • equals()는 문자열 내용을 비교합니다

Q2. 변수값이 null일 때 equals()가 오류를 발생시키는 이유는 무엇인가요?

String input = null;
input.equals("test"); // Exception

상수-우선 비교를 사용하세요:

"test".equals(input);

Q3. 대소문자를 무시하고 문자열을 비교하려면 어떻게 해야 하나요?

stringA.equalsIgnoreCase(stringB);

Q4. 알파벳 순서를 비교하려면 어떻게 해야 하나요?

a.compareTo(b);

7. 결론

올바른 비교 방법을 선택하는 것이 중요합니다

String은 레퍼런스 타입이기 때문에 잘못된 비교는 종종 예상치 못한 동작을 초래합니다. ==equals()의 차이를 이해하는 것이 필수적입니다.

체크리스트

  • == : 레퍼런스를 비교합니다
  • equals() : 내용을 비교합니다
  • equalsIgnoreCase() : 대소문자를 무시합니다
  • compareTo() : 사전 순서를 비교합니다
  • "constant".equals(variable) : null 안전합니다
  • 무거운 비교에서는 intern()이나 캐시를 사용하세요

실용적이고 필수적인 지식

문자열 비교는 로그인 검사, 입력 검증, 데이터베이스 작업, 분기 로직 및 다양한 일상 작업에 등장합니다. 올바른 기법을 알면 더 안전하고 신뢰성 높은 코드를 작성할 수 있습니다. Java에서 문자열을 다룰 때 이 가이드를 참고하세요.