본문 바로가기
Old Posts/Java

[Java] 문자열 비교하기 - equals() 메소드와 == 연산 비교

by A6K 2021. 4. 20.

자바에서 가장 오묘한 자료형은 문자열이다. 자바 프로그래머들은 문자열을 기본자료형(Primitive Type)처럼 인식하고 쓰는 경우가 많이 있지만 사실은 객체다. 때문에 두 객체의 정확한 값을 비교하는 equals() 메소드를 써야한다.

== 연산으로 두 문자열을 비교할 경우 문자열의 내용이 아니라 문자열 객체의 참조값(주소값)을 비교하기 때문에 정확히 동일한 문자열이라도 다르다고 판단할 수 있다.

문자열의 비교  '==' 연산 vs 'equals()' 메소드

문자열의 내용이 동일하더라도 JVM 메모리에서 각자 다른 객체로 존재한다면 '==' 연산은 다르다고 판단할 것이다. 다음 코드를 보자.

public class Example {

    public static void main(String[] args) {
        String str1 = new String("test");
        String str2 = new String("test");

        System.out.println(str1 == str2);
    }
}

"test"라는 동일한 문자열 객체를 선언해서 비교를 하고 있다. 얼핏 생각해보면 'true'가 출력 결과로 나와야 할 것 같지만 실제로는 그렇지 않다.

str1 변수에서 참조하는 문자열과 str2에서 참조하는 문자열은 각각 new 연산을 이용해 생성된 별도의 객체다. 비록 두 문자열 객체가 담고 있는 데이터는 "test"로 동일하지만 '==' 연산은 객체의 참조값(주소값)을 비교하기 때문에 서로 다른 객체를 다르다고 한 것이다.

따라서 문자열의 내용이 같은지를 판단하려면 equals() 메소드를 이용해야한다.

public class Example {

    public static void main(String[] args) {
        String str1 = new String("test");
        String str2 = new String("test");

        System.out.println(str1.equals(str2));
    }
}

equals() 메소드는 문자열 객체의 주소값이 아니라 문자열을 이루고 있는 문자 하나하나를 비교해서 완전히 동일한 경우에만 같다고 판단한다.

참고로 Java 8 기준 문자열 객체의 equals() 메소드의 구현은 다음과 같다. 참고로 확인해보자.

/**
 * Compares this string to the specified object.  The result is {@code
 * true} if and only if the argument is not {@code null} and is a {@code
 * String} object that represents the same sequence of characters as this
 * object.
 *
 * @param  anObject
 *         The object to compare this {@code String} against
 *
 * @return  {@code true} if the given object represents a {@code String}
 *          equivalent to this string, {@code false} otherwise
 *
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

문자열을 구성하고 있는 문자 하나하나를 비교해서 모든 문자가 정확하게 동일할 경우에 같다고 판단한다.

'==' 연산이 두 문자열을 같다고 판단하는 경우도 있긴하다. 다음 코드를 보자.

public class Example {

    public static void main(String[] args) {
        String str1 = "test";
        String str2 = "test";

        System.out.println(str1 == str2);
    }
}

이 경우 별도로 선언된 두 개의 문자열을 비교했다. 결과는?

같다고 판단한다.

문자열을 선언하는 방법에는 두 가지가 있다. 

  • new를 이용한 객체 선언
  • 리터럴 선언

포스트 초반에 본 선언 방식은 new String()을 이용해서 객체를 선언했다. 이 방식은 힙 메모리 영역에 문자열 객체를 선언하는 방식으로 new를 이용해서 새로 만들때마다 새로운 객체가 생겨난다.

두번째 코드에서 본 것처럼 선언하는 방식은 리터럴 선언이라고 한다. 리터럴로 선언된 문자열의 경우 String Pool이라고하는 곳에 문자열이 저장된다. 리터럴로 선언된 문자열은 컴파일러가 최적화를 위해 intern() 메소드를 붙여준다. 즉 다음 두 줄은 같은 동작을 한다.

String str1 = "test";
String str2 = new String("test").intern();

internal() 메소드를 호출하면 String Pool에서 문자열이 이미 존재하는지 먼저 체크한다. 문자열이 이미 String Pool에 존재하면 그 문자열의 참조를 리턴한다. 만약 문자열이 String Pool에 없으면 새로 만들어진 문자열을 String Pool에다가 넣고 그 객체에 대한 참조를 리턴해준다.

다시 위 코드로 돌아가서 동일하게 "test" 문자열을 참조하고 있는 두개의 변수는 결국 하나의 new String("test") 문자열에 대한 참조를 들고 있게된다. (참고로 문자열은 불변(Immutable)이다. 문자열에 대한 수정은 새로운 문자열 객체를 생성한다.)

자바에서 문자열을 다룰 때에는 이런 개념들을 잘 이해하고 사용하는 것이 중요하다.

댓글