문자열 비교할 때 == 사용하면 안되는 이유

한시온·2022년 3월 9일
0
post-thumbnail

배경

문자열 비교를 하는 코드를 짜다가 다음과 같은 경고 메시지가 떴습니다.

String values are compared using '==', not 'equals()'

== 비교연산자로도 충분히 비교할 수 있을 것 같은데, 왜 굳이 String 클래스의 equals() 메서드를 사용하라고 하는 걸까요?

이 주제는 이미 많은 블로그에서 다뤘고 글과 그림으로 정말 잘 설명되어 있지만, 저는 좀더 공신력 있는 정보를 얻고 싶었습니다. 그래서 이 글은 Oracle 사의 The Java® Language Specification (이하 "JLS")과 The Java® Virtual Machine Specification (이하 "JVMS") 의 상당 부분을 인용했습니다. 해당 문서를 인용할 때에는 발췌한 부분을 큰 제목 바로 아래에 따로 표기했습니다.


Class String

JLS §4.3.3 The Class String

Instances of class String represent sequences of Unicode code points.
A String object has a constant (unchanging) value.
String literals (§3.10.5) are references to instances of class String.

JLS §3.10.5 String Literals

A string literal is a reference to an instance of class String (§4.3.1, §4.3.3).
Moreover, a string literal always refers to the same instance of class String. This
is because string literals - or, more generally, strings that are the values of constant
expressions (§15.28) - are "interned" so as to share unique instances, using the
method String.intern (§12.5).

String 객체는 문자열에 대한 유니코드 코드값(Unicode code points)을 가집니다.
그리고 String literalString 객체를 참조합니다.

	String s = "Hello";

위의 코드에서 String literal"Hello"입니다.
String literal은 항상 같은 인스턴스를 참조합니다.

JLS §12.5 Creation of New Class Instances

Loading of a class or interface that contains a string literal (§3.10.5) may create a
new String object to represent the literal. (This will not occur if a string denoting
the same sequence of Unicode code points has previously been interned.)

예를 들어 string literal은 새로운 문자열에 대한 유니코드 코드값이 이미 Constant Pool에 등록되어 있다면 기존의 String 인스턴스를 참조합니다. 그렇지 않으면 String 객체를 생성하고 새로운 인스턴스를 참조합니다.

The Constant Pool

JVMS §4.4 §The Constant Pool

Java Virtual Machine instructions do not rely on the run-time layout of classes,
interfaces, class instances, or arrays. Instead, instructions refer to symbolic
information in the constant_pool table.
All constant_pool table entries have the following general format:
cp_info {
u1 tag;
u1 info[];
}

Constant pool은 문자열뿐만 아니라 숫자 리터럴과 클래스, 메서드에 대한 참조 등을 포함하는 정보입니다. tag를 통해 type을 구분하고 info[]에는 각 type별 데이터가 포함됩니다.
문자열은 CONSTANT_String_info 구조를 따르며 이때 tag == 8입니다.

JVMS §4.4.3 §The CONSTANT_String_info Structure

The CONSTANT_String_info structure is used to represent constant objects of the
type String:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
The items of the CONSTANT_String_info structure are as follows:
tag
The tag item has the value CONSTANT_String (8).
string_index
The value of the string_index item must be a valid index into the
constant_pool table. The constant_pool entry at that index must be a
CONSTANT_Utf8_info structure (§4.4.7) representing the sequence of Unicode
code points to which the String object is to be initialized.

CONSTANT_String_info 구조에서 string_indexCONSTANT_Utf8_info 형식으로 문자열에 대한 유니코드 코드값을 나타내게 됩니다.

JVMS §5.1 The Run-Time Constant Pool

The static constants in the run-time constant pool are also derived from entries in
the constant_pool table in accordance with the structure of each entry:
• A string constant is a reference to an instance of class String, and is derived
from a CONSTANT_String_info structure (§4.4.3). To derive a string constant,
the Java Virtual Machine examines the sequence of code points given by the
CONSTANT_String_info structure:
– If the method String.intern has previously been invoked on an instance of
class String containing a sequence of Unicode code points identical to that
given by the CONSTANT_String_info structure, then the string constant is a
reference to that same instance of class String.
– Otherwise, a new instance of class String is created containing the sequence
of Unicode code points given by the CONSTANT_String_info structure. The
string constant is a reference to the new instance. Finally, the method
String.intern is invoked on the new instance.

string constant 또는 string literalString 클래스의 인스턴스를 참조합니다. 이때 constant pool에서 문자열과 동일한 유니코드 코드값이 있으면 존재하는 인스턴스를 참조합니다. 만약 동일한 유니코드 코드값이 없다면 CONSTANT_String_info 구조에 따라 해당 유니코드 코드값을 가지는 새로운 String 클래스의 인스턴스를 참조합니다.

지금까지 String 객체가 어떻게 생성되고 JVMconstant pool을 통해 어떻게 관리되는지 알아보았습니다. 이제 마지막으로 equality operator(==)에 대해 설명하겠습니다.

JLS §15.21 equality operator

While == may be used to compare references of type String, such an equality test
determines whether or not the two operands refer to the same String object. The
result is false if the operands are distinct String objects, even if they contain the
same sequence of characters (§3.10.5). The contents of two strings s and t can be
tested for equality by the method invocation s.equals(t).

JLS에 따르면 자바에서 비교연산자, 그중에서 equality operator(==)의 역할은 크게 3가지입니다.

  1. Numerical Equality Operator
  2. Boolean Equality Operator
    3. Reference Equality Operator

String 타입에 대한 동등비교는 3번째에 해당합니다. String literalString 클래스의 인스턴스에 대한 참조이기 때문입니다(§3.10.5 "String Literals" 참고).

이 경우 equality operator는
1. 두 피연산자(operand)가 같은 String 객체를 참조하는지 검사합니다.
1-1. 각 string literal이 같은 String 객체를 참조한다면 True,
1-2 서로 다른 String 객체를 참조한다면 False입니다.

이때 문자열의 내용(sequence of characters)이 같은지의 여부는 영향을 주지 않습니다. 다시 말해 equality operator로 비교하는 경우에는 비교하려는 두 문자열이 같아도 참조하는 String 객체가 다르면 False 입니다.

정리하자면 문자열에 대한 equality operator는 내용이 아닌 참조에 대한 비교를 수행합니다. 두 문자열의 내용(the content of two strings)에 대한 비교는 equals() 메서드로 가능하다고 JLS는 기술하고 있습니다.

두 문자열을 비교하는 주 목적은 내용에 대한 비교이지 리터럴이 참조하는 객체에 대한 비교가 아니기 때문에 문자열을 비교할 때는 ==보다 equals() 메서드를 사용하는 것이 오류를 방지할 수 있는 측면에서 안전하다고 여겨집니다.

보다 심화된 내용은
https://www.latera.kr/blog/2019-02-09-java-string-intern/
을 보시면 좋을 것 같네요.


출처

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#intern()
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#equals(java.lang.Object)
https://docs.oracle.com/javase/specs/jls/se11/html/index.html
https://docs.oracle.com/javase/specs/jvms/se11/html/index.html
https://www.latera.kr/blog/2019-02-09-java-string-intern/

끝.

profile
가볍고 무겁게

0개의 댓글