2.6Sprachabhängiges Vergleichen und Normalisierung *
Für die deutsche Sprache gilt, dass »ä« zwischen »a« und »b« äquivalent zu »ae« einsortiert wird und nicht so, wie Unicode das Zeichen einordnet: hinter dem »z«. Ähnliches gilt für das »ß«. Auch das Spanische hat seine Besonderheiten im Alphabet: Hier gelten das »ch« und das »ll« als einzelner Buchstabe, die passend einsortiert werden müssen.
Damit Java für alle Landessprachen die String-Vergleiche korrekt durchführen kann, bietet die Bibliothek Collator-Klassen.
2.6.1Die Klasse Collator
Mit den java.text.Collator-Objekten ist es möglich, Zeichenketten nach jeweils landesüblichen Kriterien zu vergleichen. So werden die Sprachbesonderheiten jedes Landes beachtet. Ein Collator-Objekt wird vor seiner Benutzung mit getInstance() erzeugt.
[zB]Beispiel
Das »Ä« liegt zwischen »A« und »B«:
Listing 2.21CollatorDemo.java
System.out.println( col.compare( "Armleuchter", "Ätsch" ) ); // –1
System.out.println( col.compare( "Ätsch", "Bätsch" ) ); // –1
Die statische Fabrikmethode getInstance(…) nimmt optional einen Ländercode als Locale-Objekt an. Explizit setzt getInstance(Locale.GERMAN) das Vergleichsverfahren für deutsche Zeichenketten; die Länderbezeichnung ist in diesem Fall eine Konstante der Locale-Klasse. Standardmäßig nutzt getInstance() die aktuelle Einstellung des Systems.
implements Comparator<Object>, Cloneable
static Collator getInstance()
Liefert einen Collator für die aktuelle Landessprache.static Collator getInstance(Locale desiredLocale)
Liefert einen Collator für die gewünschte Sprache.abstract int compare(String source, String target)
Vergleicht die beiden Zeichenketten auf ihre Ordnung. Der Rückgabewert ist entweder <0, 0 oder >0.int compare(Object o1, Object o2)
Vergleicht die beiden Argumente auf ihre Ordnung. Ruft compare((String)o1, (String)o2) auf.
Vergleichsarten
Die Collator-Klasse deklariert sinnvolle Methoden, die über die Vergleichsmöglichkeiten der String- und StringBuffer-/StringBuilder-Klasse hinausgehen. So ist es über die Methode setStrength(…) möglich, unterschiedliche Vergleichsarten einzustellen. Die Collator-Klasse deklariert vier Strenge-Konstanten:
PRIMARY: Erkennt Unterschiede im Grundzeichen, sodass »a« kleiner »b« ist. Es gibt keine Unterschiede durch Akzente und Umlaute, sodass »a«, »ä« und »á« gleich sind.
SECONDARY: Erkennt Zeichen mit Akzenten. So sind »a« und »á« nicht mehr gleich wie bei PRIMARY.
TERTIARY: Unterscheidet in der Groß- und Kleinschreibung; bei PRIMARY und SECONDARY ist die Schreibweise egal, und »a« ist gleich »A«.
IDENTICAL: Wirklich alle Unicode-Zeichen sind anders. Während die ersten drei Konstanten nicht sichtbare Buchstaben wie '\u0001' oder '\u0006' gleich behandeln, sind sie unter IDENTICAL wirklich unterschiedlich.
Was die einzelnen Werte für jede Sprache bedeuten, beschreibt der Unicode-Standard präzise. Beispielsweise erkennt der tolerante Vergleich »abc« und »ABC« als gleich. Ohne explizit gesetztes setStrength(…) ist der Standard TERTIARY:
Listing 2.22CollatorStrengthDemo.java
import java.text.*;
class CollatorStrengthDemo {
static void compare( Collator col, String a, String b ) {
if ( col.compare( a, b ) < 0 )
System.out.println( a + " < " + b );
if ( col.compare( a, b ) == 0 )
System.out.println( a + " = " + b );
if ( col.compare( a, b ) > 0 )
System.out.println( a + " > " + b );
}
public static void main( String[] args ) {
Collator col = Collator.getInstance( Locale.GERMAN );
System.out.println( "Strength = PRIMARY" );
col.setStrength( Collator.PRIMARY );
compare( col, "abc", "ABC" );
compare( col, "Quäken", "Quaken" );
compare( col, "boß", "boss" );
compare( col, "boß", "boxen" );
System.out.printf( "%nStrength = SECONDARY%n" );
col.setStrength( Collator.SECONDARY );
compare( col, "abc", "ABC" );
compare( col, "Quäken", "Quaken" );
compare( col, "boß", "boss" );
compare( col, "boß", "boxen" );
System.out.printf( "%nStrength = TERTIARY%n" );
col.setStrength( Collator.TERTIARY );
compare( col, "abc", "ABC" );
compare( col, "Quäken", "Quaken" );
compare( col, "boß", "boss" );
compare( col, "boß", "boxen" );
}
}
Die Ausgabe ist folgende:
abc = ABC
Quäken = Quaken
boß = boss
boß < boxen
Strength = SECONDARY
abc = ABC
Quäken > Quaken
boß = boss
boß < boxen
Strength = TERTIARY
abc < ABC
Quäken > Quaken
boß > boss
boß < boxen
2.6.2Effiziente interne Speicherung für die Sortierung
Obwohl sich mit der Collator-Klasse sprachspezifische Vergleiche korrekt umsetzen lassen, ist die Geschwindigkeit gegenüber einem normalen String-Vergleich geringer. Daher bietet die Collator-Klasse die Objektmethode getCollationKey(…) an, die ein CollationKey-Objekt liefert, das schnellere Vergleiche zulässt.
CollationKey key1 = col.getCollationKey( "ätzend" );
CollationKey key2 = col.getCollationKey( "Bremsspur" );
Durch CollationKeys lässt sich die Performance bei Vergleichen zusätzlich verbessern, da der landesspezifische String in einen dazu passenden, normalen Java-String umgewandelt wird, der dann schneller gemäß der internen Unicode-Zeichenkodierung verglichen werden kann. Dies bietet sich zum Beispiel beim Sortieren einer Tabelle an, wo mehrere Vergleiche mit einem gleichen String durchgeführt werden müssen. Der Vergleich wird mit compareTo(CollationKey) durchgeführt.
[zB]Beispiel
Der Vergleich von key1 und key2 lässt sich durch folgende Zeile ausdrücken:
Das Ergebnis ist wie bei der compare(…)-Methode bei Collator-Objekten entweder <0, 0 oder >0.
implements Comparable<CollationKey>
int compareTo(CollationKey target)
Vergleicht zwei CollationKey-Objekte miteinander.int compareTo(Object o)
Vergleicht den aktuellen CollationKey mit dem angegebenen Objekt. Ruft lediglich compareTo((CollationKey)o) auf.byte[] toByteArray()
Konvertiert den CollationKey in eine Folge von Bytes.boolean equals(Object target)
Testet die beiden CollationKey-Objekte auf Gleichheit.String getSourceString()
Liefert den String zum CollationKey.int hashCode()
Berechnet den Hashcode für den CollationKey.
implements Comparator<Object>, Cloneable
abstract CollationKey getCollationKey(String source)
Liefert einen CollationKey für den konkreten String.
2.6.3Normalisierung
Die Klasse java.text.Normalizer ermöglicht eine Unicode-Normalisierung (http://unicode.org/faq/normalization.html). Die Klasse bietet zwei einfache statische Methoden:
boolean isNormalized(CharSequence src, Normalizer.Form form)
Der Normalizer normalisiert oder testet nach den Vorgaben des Unicode-Standards (http://www.unicode.org/reports/tr15/) einen String nach den Normalisierungsformaten NFC, NFD, NFKC und NFKD.
[zB]Beispiel
Normalisiere einen String:
System.out.println( s ); // aa?u?o?n?
System.out.println( Arrays.toString( s.getBytes( StandardCharsets.ISO_8859_1 ) ) );
// [97, 97, 63, 117, 63, 111, 63, 110, 63]
Die übrig gebliebenen Striche und Punkte lassen sich einfach entfernen, denn so ist das Ergebnis nicht sonderlich nützlich.
[zB]Beispiel
Ersetze in einem String alle diakritischen Zeichen:
s = Normalizer.normalize( s, Normalizer.Form.NFD );
s = s.replaceAll( "[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+", "" );
System.out.println( s ); // Muller
Die Lösung geht zweistufig vor. Der Normalisierer zerlegt zunächst den String und macht die eigentliche Arbeit. replaceAll(…) entfernt dann übrig gebliebene Punkte, Striche, Kreise und Häkchen.