String 오브젝트는 다양한 방법으로 비교가 가능하며 그 결과 또한 종종 차이가 날 수 있다. 결과의 정확성은 주로 어떤 유형의 비교가 요구되느냐에 의해 좌우된다. 일반적인 비교 기법으로는 다음과 같은 것들이 있다.
== 연산자에 의한 비교.
String 오브젝트의 equals 메소드에 의한 비교.
String 오브젝트의 compareTo 메소드에 의한 비교.
Collator 오브젝트에 의한 비교.
== 연산자에 의한 비교
== 연산자는 String 오브젝트 레퍼런스에 사용되는데, 2개의 String 변수가 메모리 내의 동일한 오브젝트를 가리킬 경우 비교값으로 true를 리턴한다 . 그렇지 않은 경우, 텍스트가 동일한 문자 값을 가지는지 여부에 관계없이 비교값으로 false 를 리턴한다. == 연산자는 실제 char 데이터를 비교하지 않는데, 이 점을 간과할 경우 작업자는 그 후의 코드에 'The strings are unequal(문자열이 동일하지 않다)' 라고 나타나는 것에 당황해 할 수도 있다.
String name1 = "Michèle";
String name2 = new String("Michèle");
if (name1 == name2) {
System.out.println("The strings are equal.");
} else {
System.out.println("The strings are unequal.");
자바 플랫폼은 문자열 리터럴 및 상수를 위한 내부 풀을 생성한다. 정확하게 동일한 char 값을 가지는 문자열 리터럴과 상수는 풀 내에 단 한번 존재하게 된다. 또한 String 리터럴 및 상수를 동일 char 값과 비교하면 그 결과가 항상 동일하다는 것을 알 수 있다.
equals 메소드에 의한 비교
equals 메소드는 두 문자열의 실제 char 내용을 비교하는데, 이 메소드는 2개의 String 오브젝트가 동일한 값을 가지는 char 데이터를 보유할 경우 true 를 반환한다. 다음의 코드 예제에서는 'The strings are equal(문자열이 동일하다)' 이 출력된다.
String name1 = "Michèle";
String name2 = new String("Michèle");
if (name1.equals(name2) {
System.out.println("The strings are equal.");
} else {
System.out.println("The strings are unequal.");
compareTo 메소드에 의한 비교
compareTo 메소드는 equals 메소드와 유사하게 char 값을 비교한다. 아울러, 이 메소드는 자체 String 오브젝트가 인수 문자열에 선행할 경우 음의 정수를 반환하고, 문자열이 동일할 경우에는 0을 반환한다. 반면에 오브젝트가 인수 문자열 뒤에 올 경우에는 양의 정수를 반환한다. compareTo 메소드는 cat 이 hat 에 선행한다는 것을 나타낸다. 이 비교에서 알아두어야 할 가장 중요한 점은 메소드가 char 값을 축어적으로 비교한다는 점인데, 가령 메소드는 cat 의 'c' 값이 hat 의 'h'보다 적은 숫자 값을 가진다고 판단한다.
String w1 = "cat";
String w2 = "hat";
int comparison = w1.compareTo(w2);
if (comparison < 0) {
System.out.printf("%s < %s\n", w1, w2);
} else {
System.out.printf("%s < %s\n", w2, w1);
위의 코드 예제는 compareTo 메소드의 동작을 보여주며 cat < hat 을 출력한다. 우리가 예상한 결과이긴 한데, 그렇다면 이 경우 어떤 결함과 문제점이 생길 수 있는가?
오류 산출
사전을 이용할 때처럼 텍스트를 자연어로서 비교하려 하면 문제가 발생하게 된다. String 클래스는 자연어의 관점에서 텍스트를 비교할 능력을 가지고 있지 않은데, 이 클래스의 equals 와 compareTo 메소드는 문자열의 개별 char 값을 비교한다. name1 의 인덱스 n 에 해당하는 char 값과 name2 의 인덱스 n 에 해당하는 char 값이 양 문자열의 모든 n 에 대해서 동일할 경우 equals 메소드는 true 값을 리턴한다.
동일한 compareTo 메소드에 대해 cat 와 Hat 의 비교를 요구할 경우 혼란스런 결과가 산출된다. 일반적으로 사람들은 대문자 여부와 관계없이 cat 이 Hat 보다 앞선다는 것을 알고 있다. 하지만, compareTo 메소드는 Hat < cat 으로 표시하는데, 이유인 즉 유니코드 문자표에서는 대문자가 소문자에 선행하기 때문에 이런 결과가 나오는 것이다. ASCII 문자표에서도 같은 순서가 적용된다. 따라서 분명한 것은 애플리케이션 사용자에게 정렬된 텍스트를 제시하고자 할 경우 이런 순서가 항상 바람직한 것은 아니라는 점이다.
문자열의 동일성을 판단하려고 할 때에도 잠재적인 문제점이 나타날 수 있다. 즉, 텍스트는 복수의 내부 표현을 가질 수 있는데, 가령 'Michèle'이라는 불어 이름에는 유니코드 문자 시퀀스 M i c h è l e 이 포함된다. 하지만, 시퀀스 M i c h e ` l e 을 사용할 수도 있다. 두 번째는 '시퀀스 조합'('e' '`')을 사용하여 'è'를 표시하는 경우이다. 유니코드를 이해하는 그래픽 시스템은 이 두 표현의 내부 문자 시퀀스가 약간 다르더라도 동일하게 보이도록 디스플레이할 것이고, String 오브젝트의 단순한(simplistic) equals 메소드는 이 두 문자열이 다른 텍스트를 가지는 것으로 표시한다. 즉, 이들은 사전적으로(lexicographically) 동일한 것이 아니라 언어학적으로(linguistically) 동일한 것이다.
아래의 코드 단편으로는 'The strings are unequal.'이 나타난다. equals 와 compareTo 메소드 모두 이들 문자열의 언어학적 동일성을 이해하지 못한다.
String name1 = "Michèle";
String name2 = "Miche\u0300le"; //U0300 is the COMBINING GRAVE ACCENT
if (name1.equals(name2)) {
System.out.println("The strings are equal.");
} else {
System.out.println("The strings are unequal.");
이름들의 목록을 정렬하려고 할 경우, 문자열의 compareTo 메소드는 거의 십중팔구 잘못된 결과를 표시하게 된다. 특정 이름을 검색하고자 할 경우, 사용자가 시퀀스 조합을 입력하거나 데이터베이스가 사용자의 입력과 다르게 데이터를 정규화(normalize)하면 이번에도 equals 메소드는 엉뚱한 결과를 표시한다. 문제는, 자연어 정렬 또는 검색을 수행할 때마다 문자열의 단순한 비교가 잘못된 결과를 표시한다는 것인데, 이런 연산을 수행하려면 단순한 char 값 비교보다는 더 강력한 무언가가 필요하다.
Collator 사용하기
java.text.Collator 클래스는 자연어 비교 기능을 제공하는데, 자연어 비교는 주로 특정 서체의 문자들에 대한 동일성과 순서를 결정하는 지역 고유의(locale-specific) 규칙에 의존한다.
Collator 오브젝트는 사전에서 'cat'이 'Hat'보다 먼저 나온다는 사실을 이해하므로 collator 비교를 이용할 경우 다음 코드는 cat < Hat 을 출력하게 된다.
Collator collator = Collator.getInstance(new Locale("en", "US"));
int comparison = collator.compare("cat", "Hat");
if (comparison < 0) {
System.out.printf("%s < %s\n", "cat", "Hat");
} else {
System.out.printf("%s < %s\n", "Hat", "cat" );
collator는 경우에 따라(대개는 자연어 처리가 중요한 경우) 문자 시퀀스 M i c h è l e 이 M i c h e ` l e 과 동일하다는 것을 알고 있다.
다음 비교에서는 Collator 오브젝트가 사용되는데, 이는 시퀀스 조합을 인식하여 두 문자열이 동일한 것으로 판단한다. 따라서 ' The strings are equal.'이라고 나타난다.
Collator collator = Collator.getInstance(Locale.US);
String name1 = "Michèle";
String name2 = "Miche\u0300le";
int comparison = collator.compare(name1, name2);
if (comparison == 0) {
System.out.println("The strings are equal.");
} else {
System.out.println("The string are unequal.");
Collator 오브젝트는 심지어 몇 단계로 구분하여 문자 차이를 이해할 수 있다. 가령 e 와 d 는 2개의 서로 다른 글자인데, 이런 차이는 '1차적' 차이이다. 한편 글자 e 와 è 도 역시 다르지만, 그 차이는 '2차적' 특성을 띤다 할 수 있다. 또한 Collator 인스턴스 구성 방식에 따라 단어 'Michèle'과 'Michele'을 동일하게 간주할 수도 있다. 다음 코드는 'The strings are equal.' 을 출력한다.
Collator collator = Collator.getInstance(Locale.US);
collator.setStrength(Collator.PRIMARY);
int comparison = collator.compare("Michèle", "Michele");
if (comparison == 0) {
System.out.println("The strings are equal.");
} else {
System.out.println("The string are unequal.");
요약
어떤 경우에 equals 메소드가 == 연산자보다 적절한지 한 번 생각보기 바란다. 또한, 텍스트에 순서를 매겨야 할 경우 Collator 오브젝트의 자연어 비교가 필요한지 여부에 대해서도 생각해보라. 다양하고 미묘한 비교의 차이점을 살펴보고 나면, 경우에 따라 잘못된 API를 사용해 왔다는 사실을 발견하게 될지도 모른다. 이렇게 차이점을 알게 되고 나면 애플리케이션과 고객을 위해 올바른 선택을 하는 데 큰 도움이 될 수 있을 것이다.
추가 정보
본 테크 팁의 주제에 관한 자세한 내용을 보려면 다음 리소스를 참조할 것(영문)
|