본문 바로가기

프로그래밍/JAVA

자바 String 문자열.. - When are two Strings EQUAL?

String 클래스는 자바 개발자에게 있어서 다른 클래스 객체와는 다르게 다뤄진다.
먼저 다음과 같이 new String을 하여 초기화 할 수 있다.

String string = new String("The Plming Space");    ← 이러한 방법을 추천하지는 않는다.

혹은 다음과 같이 초기화도 가능하다.

String string = "The Plming Space";

문자열을 비교할 때는 equals 메서드와 혹은 == 동등연산자를 이용해서 비교가 가능하고,
때에 따라서 어떤 경우에 true를 리턴하는지를 확인하는 것이 여기서의 목표이다.

다음의 코드를 확인하면,

class Equals {

public static void main(String[] args) {
Double object1 = new Double("7.2");                               // 두 개의 Double 객체 (Rapper Class이용)
Double object2 = new Double("7.2");
System.out.println
("For Double objects both 7.2");
System.out.print("\t object1 == object2 is " );
System.out.println(object1 == object2);
System.out.print("\t object1.equals(object2) is ");
System.out.println(object1.equals(object2));

double prim1 = 7.2;                                                     // 두 개의 primitives double 값
double prim2 = 7.2;
System.out.println
("For double primitives both 7.2");
System.out.print("\t prim1 == prim2 is " );
System.out.println(prim1 == prim2);

String string1 = "7.2";                                              // 두 개의 String 문자열의 값
String string2 = "7.2";
System.out.println("For Strings both 7.2");
System.out.print("\t string1 == string2 is " );
System.out.println(string1 == string2);
System.out.print("\t string1.equals(string2) is ");
System.out.println(string1.equals(string2));
}
}

결과값 (라인값은 편의상 붙인 것임)
1: For Double objects both 7.2
2: object1 == object2 is false 
3: object1.equals(object2) is true
4: For double primitives both 7.2
5: prim1 == prim2 is true
6: For Strings both 7.2
7: string1 == string2 is true
8: string1.equals(string2) is true

object1.equals(object2) 에서는 true이다. 왜냐면 두 개는 동일한 값을 가지고 있기 때문이다.
하지만, 동등연산자로 비교한 2번 라인에서는 false이다. 이는 두 개의 객체가 서로 다른 객체이기 때문이다.
5번 라인의 primitive 타입의 동등연산자 비교는 true이다. 이는 primitive는 단순히 값만을 비교하기 때문이며
두 개의 값은 동일하기 때문에 true를 리턴하게 된다. 또한, primitive는 객체가 아니기 때문에 equals()를 이용해서 비교할 수는 없다.
 문자열 string1과 2의 비교에서는 equals()와 == 동등연산자 모두 true 값을 리턴하게 된다. 이러한 결과는
The Java Language Specification 에 설명되어 있다. "동일 패키지내의 동일한 클래스 안에 있는 리터럴 문자열은 동일한 String 객체를 참조한다."

 The Java Language Specification에서 "문자열 리터럴은 String.intern() 메소드를 사용하여 유일한 인스턴스들을 공유하므로서 'interned'된다. (아래 예제를 참고하면 이해할 수 있다.)

 다시 다음과 같은 예제를 살펴보면,

class NewEquals {

public static void main(String[] args) {
String s1 = new String("Plming");
String s2 = new String("Plming");
String s3 = "Plming";

System.out.println("For new Strings s1 and s2");
System.out.print("\t s1 == s2 is ");
if (s1 == s2)
System.out.println("true");
else
System.out.println("false");
System.out.print("\t " + "s1.equals(s2) is ");
if (s1.equals(s2))
System.out.println("true");
else
System.out.println("false");

System.out.println("For Strings s1 and s3");
System.out.print("\t s1 == s3 is ");
if (s1 == s3)
System.out.println("true");
else
System.out.println("false");
System.out.print("\t " + "s1.equals(s3) is ");
if (s1.equals(s3))
System.out.println("true");
else
System.out.println("false");
}
}

결과값
1: For new Strings s1 and s2
2: s1 == s2 is false
3: s1.equals(s2) is true
4: For Strings s1 and s3
5: s1 == s3 is false
6: s1.equals(s3) is true

3개의 문자열은 모두 같은 값으로 생성되었다. 그래서 equals()를 이용하여 비교하면, 모두 true 값을 얻을 수 있다. 그러나, s1과 s2는 서로 다른 객체이다.(당연히 new를 이용했으므로) 이 둘은 동일한 값으로 생성되었으에도 불구하고 서로 다른 객체이기 때문에 == 동등연산자를 이용하여 문자열을 비교한 2번, 5번 라인의 결과값은 false가 된다.

  The Java Language Specification에서는 new 명령어를 이용해서 새로운 객체를 생성 할 경우에는 String.intern 메소드를 거치지 않지만, 문자열 리터럴의 값은 String.intern을 거쳐서 나온 String이라고 한다.
 동일한 값을 가진 문자열이라도 서로 다른 객체로 생성된 것들은 다른 공간을 사용하게 된다. 이러한 것을 해결하기 위해서  The Java Language Specification에서는 intern() 메소드를 사용하여 문자열들이 공통의 문자열 pool을 사용할 수 있도록 사용자가 만들 수 있다고 한다. 다음의 예제가 그것을 보여준다.


class NewInternString {

public static void main(String[] args) {
String s1 = new String("Plming");
String s2 = new String("Plming");
String s3 = "Plming";

s1 = s1.intern();                                      // intern 메소드 사용

System.out.println("For Strings s1 and s2");
System.out.print("\t s1 == s2 is " );
if (s1 == s2)
System.out.println("true");
else System.out.println("false");
System.out.print("\t " + "s1.equals(s2) is ");
if (s1.equals(s2))
System.out.println("true");
else System.out.println("false");

System.out.println("For Strings s1 and s3");
System.out.print("\t s1 == s3 is " );
if (s1 == s3)
System.out.println("true");
else System.out.println("false");
System.out.print("\t " + "s1.equals(s3) is ");
if (s1.equals(s3))
System.out.println("true");
else System.out.println("false");
}
}

결과 값

1: For new Strings s1 and s2
2: s1 == s2 is false
3: s1.equals(s2) is true
4: For Strings s1 and s3
5: s1 == s3 is true
6: s1.equals(s3) is true

 이번에는 s1을 intern() 메소드를 이용하여 s1의 문자열을 문자열 상수pool에 보내었다. 이는 기존에 문자열 상수 pool에 s1이 가지고 있는 문자열과 동일한 값이 있는지 확인을 하고 동일한 문자열이 있으면 같은 것을 가리키도록 해준다. 즉, 생성되어 있는 공간은 한 개뿐인 것이다. 여기서는 동일한 문자열을 이미 s3에서 문자열 상수pool에 만들어놓았기 때문에 s1은 s3가 만든 문자열 상수pool을 동일하게 가리킨다.

 하지만, s2 문자열의 경우에는 여전히 문자열 상수pool을 가리키지 않고 자신이 생성한 객체로 가리키기 떄문에 2번 라인에서는 false가 된다.
 
 이 내용을 종합해볼 때, 동일한 String 값을 비교하는 데 있어서 제일 확실한 방법은 equals() 메소드를 이용하는 것이라는 것을 알 수 있다. 만약 동일한 문자열 값을 가지고 작업을 할 경우에 문자열을 intern()을 이용하여 문자열 상수pool에 넣어주면, equals() 메소드가 아닌 == 동등연산자만으로도 원하는 결과값을 빠르게 얻어낼 수 있다.