Galileo Computing < openbook >Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Äußere.innere Klassen
8 Besondere Klassen der Java SE
9 Generics<T>
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Einführung in die nebenläufige Programmierung
13 Einführung in Datenstrukturen und Algorithmen
14 Einführung in grafische Oberflächen
15 Einführung in Dateien und Datenströme
16 Einführung in die <XML>-Verarbeitung mit Java
17 Einführung ins Datenbankmanagement mit JDBC
18 Bits und Bytes und Mathematisches
19 Die Werkzeuge des JDK
A Die Klassenbibliothek
Stichwort

Download:
- openbook, ca. 24,5 MB
- Aufgaben, ca. 1,1 MB
- Programme, ca. 12,8 MB
Buch bestellen
Ihre Meinung?

Spacer
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
Galileo Computing
1308 S., 10., aktualisierte Auflage, geb., mit DVD
ca. 49,90 Euro, ISBN 978-3-8362-1802-3
Pfeil4 Der Umgang mit Zeichenketten
Pfeil4.1 Von ASCII über ISO-8859-1 zu Unicode
Pfeil4.1.1 ASCII
Pfeil4.1.2 ISO/IEC 8859-1
Pfeil4.1.3 Unicode
Pfeil4.1.4 Unicode-Zeichenkodierung
Pfeil4.1.5 Escape-Sequenzen/Fluchtsymbole
Pfeil4.1.6 Schreibweise für Unicode-Zeichen und Unicode-Escapes
Pfeil4.1.7 Unicode 4.0 und Java *
Pfeil4.2 Die Character-Klasse
Pfeil4.2.1 Ist das so?
Pfeil4.2.2 Zeichen in Großbuchstaben/Kleinbuchstaben konvertieren
Pfeil4.2.3 Ziffern einer Basis *
Pfeil4.3 Zeichenfolgen
Pfeil4.4 Die Klasse String und ihre Methoden
Pfeil4.4.1 String-Literale als String-Objekte für konstante Zeichenketten
Pfeil4.4.2 Konkatenation mit +
Pfeil4.4.3 String-Länge und Test auf Leerstring
Pfeil4.4.4 Zugriff auf ein bestimmtes Zeichen mit charAt( )
Pfeil4.4.5 Nach enthaltenen Zeichen und Zeichenfolgen suchen
Pfeil4.4.6 Das Hangman-Spiel
Pfeil4.4.7 Gut, dass wir verglichen haben
Pfeil4.4.8 Phonetische Vergleiche *
Pfeil4.4.9 String-Teile extrahieren
Pfeil4.4.10 Strings anhängen, Groß-/Kleinschreibung und Leerraum
Pfeil4.4.11 Suchen und ersetzen
Pfeil4.4.12 String-Objekte mit Konstruktoren neu anlegen *
Pfeil4.5 Konvertieren zwischen Primitiven und Strings
Pfeil4.5.1 Unterschiedliche Typen in String-Repräsentationen konvertieren
Pfeil4.5.2 Stringinhalt in einen primitiven Wert konvertieren
Pfeil4.5.3 String-Repräsentation im Format Binär, Hex, Oktal *
Pfeil4.6 Veränderbare Zeichenketten mit StringBuilder und StringBuffer
Pfeil4.6.1 Anlegen von StringBuilder/StringBuffer-Objekten
Pfeil4.6.2 StringBuilder/StringBuffer in andere Zeichenkettenformate konvertieren
Pfeil4.6.3 Zeichen(folgen) erfragen
Pfeil4.6.4 Daten anhängen
Pfeil4.6.5 Zeichen(folgen) setzen, löschen und umdrehen
Pfeil4.6.6 Länge und Kapazität eines StringBuilder/StringBuffer-Objekts *
Pfeil4.6.7 Vergleichen von String mit StringBuilder und StringBuffer
Pfeil4.6.8 hashCode() bei StringBuilder/StringBuffer *
Pfeil4.7 CharSequence als Basistyp *
Pfeil4.8 Reguläre Ausdrücke
Pfeil4.8.1 Pattern.matches() bzw. String#matches()
Pfeil4.8.2 Die Klassen Pattern und Matcher
Pfeil4.8.3 Finden und nicht matchen
Pfeil4.8.4 Gierige und nicht gierige Operatoren *
Pfeil4.8.5 Mit MatchResult alle Ergebnisse einsammeln *
Pfeil4.8.6 Suchen und Ersetzen mit Mustern
Pfeil4.8.7 Hangman Version 2
Pfeil4.9 Zerlegen von Zeichenketten
Pfeil4.9.1 Splitten von Zeichenketten mit split()
Pfeil4.9.2 Die Klasse Scanner
Pfeil4.9.3 Die Klasse StringTokenizer *
Pfeil4.9.4 BreakIterator als Zeichen-, Wort-, Zeilen- und Satztrenner *
Pfeil4.10 Zeichenkodierungen, XML/HTML-Entitys, Base64 *
Pfeil4.10.1 Unicode und 8-Bit-Abbildungen
Pfeil4.10.2 Das Paket java.nio.charset und der Typ Charset
Pfeil4.10.3 Konvertieren mit OutputStreamWriter/InputStreamReader-Klassen *
Pfeil4.10.4 XML/HTML-Entitys ausmaskieren
Pfeil4.10.5 Base64-Kodierung
Pfeil4.11 Ausgaben formatieren
Pfeil4.11.1 Formatieren und Ausgeben mit format()
Pfeil4.11.2 Die Formatter-Klasse *
Pfeil4.11.3 Formatieren mit Masken *
Pfeil4.11.4 Format-Klassen
Pfeil4.11.5 Zahlen, Prozente und Währungen mit NumberFormat und DecimalFormat formatieren *
Pfeil4.11.6 MessageFormat und Pluralbildung mit ChoiceFormat
Pfeil4.12 Sprachabhängiges Vergleichen und Normalisierung *
Pfeil4.12.1 Die Klasse Collator
Pfeil4.12.2 Effiziente interne Speicherung für die Sortierung
Pfeil4.12.3 Normalisierung
Pfeil4.13 Zum Weiterlesen

Galileo Computing - Zum Seitenanfang

4.4 Die Klasse String und ihre MethodenZur nächsten Überschrift

Die Entwickler von Java haben eine Symbiose zwischen String als Klasse und dem String als eingebautem Datentyp vorgenommen. Die Sonderbehandlung gegenüber anderen Objekten ist an zwei Punkten abzulesen:

  • Die Sprache ermöglicht die direkte Konstruktion von String-Objekten aus String-Literalen (Zeichenketten in doppelten Anführungszeichen).
  • Die Konkatenation (Aneinanderreihung von Strings mit +) von mehreren Strings ist erlaubt, aber Plus ist für keinen anderen Objekttyp erlaubt. Es lassen sich zum Beispiel nicht zwei Point-Objekte addieren. Mit dem Plus auf String-Objekten ist also ein besonderer Operator auf der Klasse String definiert, der nicht eigenständig auf anderen Klassen definiert werden kann. Java unterstützt keine überladenen Operatoren für Klassen, und dieses Plus ist ein Abweichler.

Galileo Computing - Zum Seitenanfang

4.4.1 String-Literale als String-Objekte für konstante ZeichenkettenZur nächsten ÜberschriftZur vorigen Überschrift

Damit wir Zeichenketten nutzen können, muss ein Objekt der Klasse String vorliegen. Das Schöne ist, dass alles in doppelten Anführungszeichen schon automatisch ein String-Objekt ist. Das bedeutet auch, dass hinter dem String-Literal gleich ein Punkt für den Methodenaufruf stehen kann.

Beispiel

"Hi Chris".length() liefert die Länge der Zeichenkette. Das Ergebnis ist 8. Leerzeichen und Sonderzeichen zählen mit.

Nur Zeichenfolgen in doppelten Anführungszeichen sind String-Literale und somit schon gleich vorkonstruierte Objekte. Das gilt für StringBuilder/StringBuffer nicht – sie müssen von Hand mit new erzeugt werden. Nutzen wir String-Literale, so sollten wir ausdrücklich davon absehen, String-Objekte mit new zu erzeugen; ein s = new String("String") ist unsinnig; s = "String" ist korrekt.

Abbildung

Abbildung 4.4: UML-Diagramm der umfangreichen Klasse String


Galileo Computing - Zum Seitenanfang

4.4.2 Konkatenation mit +Zur nächsten ÜberschriftZur vorigen Überschrift

Mehrere Beispiele haben schon gezeigt, dass Strings mit + konkateniert werden können.

Beispiel

Haben die Glieder bei der Konkatenation unterschiedliche Datentypen, und einer ist String, werden diese automatisch auf String gebracht.

int    age    = 37;
double height = 1.83;
String s = "Alter: " + age + ", Größe: " + height;
System.out.println( s ); // Alter: 37, Größe: 1.83


Hinweis

Intern ist die Aneinanderreihung über Methoden der Klassen String, StringBuilder beziehungsweise StringBuffer realisiert. Die Konkatenation über + ist insbesondere in Schleifen nicht performant, und später werden wir im Abschnitt 4.6.4 bessere Lösungen kennenlernen.


Galileo Computing - Zum Seitenanfang

4.4.3 String-Länge und Test auf LeerstringZur nächsten ÜberschriftZur vorigen Überschrift

String-Objekte verwalten intern die Zeichenreihe, die sie repräsentieren, und bieten eine Vielzahl von Methoden, um die Eigenschaften des Objekts preiszugeben. Eine Methode haben wir schon benutzt: length(). Für String-Objekte ist diese so implementiert, dass die Anzahl der Zeichen im String (die Länge des Strings) zurückgegeben wird. Um herauszufinden, ob der String keine Zeichen hat, lässt sich neben length() == 0 auch die Methode isEmpty() nutzen:

Tabelle 4.6: Ergebnisse der Methoden length() und isEmpty() bei unterschiedlichen Strings

Anweisung Ergebnis
"".length() 0
"".isEmpty() true
" ".length() 1
" ".isEmpty() false
String s = null; s.length(); Fehler NullPointerException

Eine praktische Hilfsmethode: isNullOrEmpty()

Während das .NET-Framework etwa die statische Member-Funktion IsNullOrEmpty- (String) anbietet, um zu testen, ob die übergebene Referenz null oder die Zeichenkette leer ist, so muss das in Java getrennt getestet werden. Hier ist eine eigene statische Utility-Methode praktisch:

Listing 4.3: LengthAndEmptyDemo.java, isNullOrEmpty()

/**
* Checks if a String is {@code null} or empty ({@code ""}).
*
* <pre>
* StringUtils.isEmpty(null) == true
* StringUtils.isEmpty(&quot;&quot;) == true
* StringUtils.isEmpty(&quot; &quot;) == false
* StringUtils.isEmpty(&quot;bob&quot;) == false
* StringUtils.isEmpty(&quot; bob &quot;) == false
* </pre>
*
* @param str The String to check, may be {@code null}.
* @return {@code true} if the String is empty or {@code null}, {@code false}
* otherwise.
*/
public static boolean isNullOrEmpty( String str )
{
return str == null || str.length() == 0;
}

Ob der String nur aus Leerzeichen besteht, testet die Methode nicht.


Galileo Computing - Zum Seitenanfang

4.4.4 Zugriff auf ein bestimmtes Zeichen mit charAt( )Zur nächsten ÜberschriftZur vorigen Überschrift

Die vielleicht wichtigste Methode der Klasse String ist charAt(int index). Diese Methode liefert das entsprechende Zeichen an einer Stelle, die »Index« genannt wird. Dies bietet eine Möglichkeit, die Zeichen eines Strings (zusammen mit der Methode length()) zu durchlaufen. Ist der Index kleiner null oder größer beziehungsweise gleich der Anzahl der Zeichen im String, so löst die Methode eine StringIndexOutOfBoundsException[115](Mit 31 Zeichen gehört dieser Klassenname schon zu den längsten. Übertroffen wird er aber noch um fünf Zeichen von TransformerFactoryConfigurationError. Im Spring-Paket gibt es aber JdbcUpdateAffectedIncorrectNumberOfRowsException – auch nicht von schlechten Eltern.) mit der Fehlerstelle aus.

Beispiel

Liefere das erste und letzte Zeichen im String s:

String s = "Ich bin nicht dick! Ich habe nur weiche Formen.";
char first = s.charAt( 0 ); // 'I'
char last = s.charAt( s.length() – 1 ); // '.'

Wir müssen bedenken, dass die Zählung wieder bei null beginnt. Daher müssen wir von der Länge des Strings eine Stelle abziehen. Da der Vergleich auf den korrekten Bereich bei jedem Zugriff auf charAt() stattfindet, ist zu überlegen, ob der String bei mehrmaligem Zugriff nicht stattdessen einmalig in ein eigenes Zeichen-Array kopiert werden sollte.


Galileo Computing - Zum Seitenanfang

4.4.5 Nach enthaltenen Zeichen und Zeichenfolgen suchenZur nächsten ÜberschriftZur vorigen Überschrift

Die Objektmethode contains(CharSequence) testet, ob ein Teilstring (engl. substring) in der Zeichenkette vorkommt, und liefert true, wenn das der Fall ist. Groß-/Kleinschreibung ist relevant. Im nächsten Programm wollen wir testen, ob eine E-Mail mögliche Spam-Wörter enthält.

Listing 4.4: EmailSpamChecker.java

public class EMailSpamChecker
{
public static void main( String[] args )
{
String email1 = "Hallo Chris,...";
System.out.println( containsSpam( email1 ) ); // false
String email2 = "Kaufe Viagra! Noch billiga und macht noch härta!";
System.out.println( containsSpam( email2 ) ); // true
}

public static boolean containsSpam( String text )
{
return text.contains( "Viagra" ) || text.contains( "Ding-Dong-Verlängerung" );
}
}

Fundstelle mit indexOf() zurückgeben

Die Methode contains() ist nicht mit einem char überladen, kann also nicht nach einem einzelnen Zeichen suchen, es sei denn, der String bestünde nur aus einem Zeichen. Dazu ist indexOf() in der Lage: Die Methode liefert die Fundstelle eines Zeichens beziehungsweise Teilstrings. Findet indexOf() nichts, liefert sie –1.

Beispiel

Ein Zeichen mit indexOf() suchen:

String s = "Ernest Gräfenberg";
int index1 = s.indexOf( 'e' ); // 3
int index2 = s.indexOf( 'e', index1 + 1 ); // 11
Die Belegung von index1 ist 3, da an der Position 3 das erste Mal ein 'e' vorkommt. Die zweite Methode indexOf() sucht mit dem zweiten Ausdruck index1 + 1 ab der Stelle 4 weiter. Das Resultat ist 11.

Wie contains() unterscheidet die Suche zwischen Groß- und Kleinschreibung. Die Zeichen in einem String sind wie Array-Elemente ab 0 durchnummeriert. Ist das Index-Argument kleiner 0, so wird dies ignoriert und der Index automatisch auf 0 gesetzt.

Beispiel

Beschreibt das Zeichen c ein Escape-Zeichen, etwa einen Tabulator oder ein Return, dann soll die Bearbeitung weitergeführt werden:

if ( "\b\t\n\f\r\"\\".indexOf(c) >= 0 ) {
...
}
contains() konnten wir nicht verwenden, da der Parametertyp nur CharSequence, aber kein char ist.

Die indexOf()-Methode ist nicht nur mit char parametrisiert, sondern auch mit String[116](Der Parametertyp String erlaubt natürlich nur Objekte vom Typ String, und Unterklassen von String gibt es nicht. Allerdings gibt es andere Klassen in Java, die Zeichenfolgen beschreiben, etwa StringBuilder oder StringBuffer. Diese Typen unterstützt die indexOf()-Methode nicht. Das ist schade, denn indexOf() hätte statt String durchaus einen allgemeineren Typ CharSequence erwarten können, um den String sowie StringBuilder/StringBuffer zu implementieren (zu dieser Schnittstelle in Abschnitt 4.6) mehr.), um nach ganzen Zeichenfolgen zu suchen und die Startposition zurückzugeben.

Beispiel

indexOf() mit der Suche nach einem Teilstring:

String str = "In Deutschland gibt es immer noch ein Ruhrgebiet, " +
"obwohl es diese Krankheit schon lange nicht mehr geben soll.";
String s = "es";
int index = str.indexOf( s, str.indexOf(s) + 1 ); // 57
Die nächste Suchposition wird ausgehend von der alten Finderposition errechnet. Das Ergebnis ist 57, da dort zum zweiten Mal das Wort »es« auftaucht.

Vom Ende an suchen

Genauso wie am Anfang gesucht werden kann, ist es auch möglich, am Ende zu beginnen.

Beispiel

Hierzu dient die Methode lastIndexOf():

String str = "May the Force be with you.";
int index = str.lastIndexOf( 'o' ); // 23
Genauso wie bei indexOf() existiert eine überladene Version, die rückwärts ab einer bestimmten Stelle nach dem nächsten Vorkommen von »o« sucht. Wir schreiben:
index = str.lastIndexOf( 'o', index – 1 );          // 9

Die Parameter der char-orientierten Methoden indexOf() und lastIndexOf() sind alle vom Typ int und nicht, wie man spontan erwarten könnte, vom Typ char und int. Das zu suchende Zeichen wird als erstes int-Argument übergeben. Die Umwandlung des char in ein int nimmt der Java-Compiler automatisch vor, sodass dies nicht weiter auffällt. Bedauerlicherweise kann es dadurch aber zu Verwechslungen bei der Reihenfolge der Argumente kommen: Bei s.indexOf(start, c) wird der erste Parameter start als Zeichen interpretiert und das gewünschte Zeichen c als Startposition der Suche.

Anzahl der Teilstrings einer Zeichenkette *

Bisher bietet die Java-Bibliothek keine direkte Methode, um die Anzahl der Teilstrings einer Zeichenkette herauszufinden. Eine solche Methode ist jedoch schnell geschrieben:

Listing 4.5: CountMatches.java

public class CountMatches
{
public static int frequency( String source, String part )
{
if ( source == null || source.isEmpty() || part == null || part.isEmpty() )
return 0;

int count = 0;

for ( int pos = 0; (pos = source.indexOf( part, pos )) != –1; count++ )
pos += part.length();

return count;
}

public static void main( String[] args )
{
System.out.println( frequency( "schlingelschlangel", "sch" ) ); // 2
System.out.println( frequency( "schlingelschlangel", "ing" ) ); // 1
System.out.println( frequency( "schlingelschlangel", "" ) ); // 0
}
}

Galileo Computing - Zum Seitenanfang

4.4.6 Das Hangman-SpielZur nächsten ÜberschriftZur vorigen Überschrift

Geschätzte 90 Prozent aller Praxisaufgaben lösen die Methoden, die wir bisher schon kennengelernt haben:

  • int charAt(int)
  • int length()
  • boolean equals(Object)
  • boolean contains(CharSequence)
  • int indexOf(char), int indexOf(String)

Genau die Methoden wollen wir für ein kleines Spiel nutzen, das berühmte Hangman-Spiel. Hierbei geht es darum, alle Buchstaben eines Wortes zu raten. Am Anfang ist jeder Buchstabe durch einen Unterstrich unkenntlich gemacht. Der Benutzer fängt an zu raten und füllt nach und nach die einzelnen Platzhalter aus. Gelingt es dem Spieler nicht, nach einer festen Anzahl von Runden das Wort zu erraten, hat er verloren.

Listing 4.6: Hangman1.java

public class Hangman1
{
public static void main( String[] args )
{
String hangmanWord = "alligatoralley";
String usedChars = "";

String guessedWord = "";
for ( int i = 0; i < hangmanWord.length(); i++ )
guessedWord += "_";

for ( int guesses = 1; ; )
{
if ( guesses == 10 )
{
System.out.printf( "Nach 10 Versuchen ist jetzt Schluss. Sorry! "+
"Apropos, das Wort war '%s'.", hangmanWord );
break;
}

System.out.printf( "Runde %d. Bisher geraten: %s. Was wählst du für ein
Zeichen?%n", guesses, guessedWord );
char c = new java.util.Scanner( System.in ).next().charAt( 0 );
if ( usedChars.indexOf( c ) >= 0 )
{
System.out.printf( "%c hast du schon mal getippt!%n", c );
guesses++;
}
else // Zeichen wurde noch nicht benutzt
{
usedChars += c;
if ( hangmanWord.indexOf( c ) >= 0 )
{
guessedWord = "";
for ( int i = 0; i < hangmanWord.length(); i++ )
guessedWord += usedChars.indexOf( hangmanWord.charAt( i ) ) >= 0 ?
hangmanWord.charAt( i ) : "_";

if ( guessedWord.contains( "_" ) )
System.out.printf( "Gut geraten, '%s' gibt es im Wort. " +
"Aber es fehlt noch was!%n", c );
else
{
System.out.printf( "Gratulation, du hast das Wort '%s' erraten!",
hangmanWord );
break;
}
}
else // hangmanWord.indexOf( c ) == –1
{
System.out.printf( "Pech gehabt, %c kommt im Wort nicht vor!%n", c );
guesses++;
}
}
}
}
}

Galileo Computing - Zum Seitenanfang

4.4.7 Gut, dass wir verglichen habenZur nächsten ÜberschriftZur vorigen Überschrift

Um Strings zu vergleichen, gibt es viele Möglichkeiten und Optionen:

  • Die Methode equals() der Klasse String achtet auf absolute Übereinstimmung.
  • Die Methode equalsIgnoreCase() der Klasse String ist für einen Vergleich zu haben, der unabhängig von der Groß-/Kleinschreibung ist.
  • Seit Java 7 erlaubt switch den Vergleich von String-Objekten mit einer Liste von Sprungzielen. Der Vergleich wird intern mit equals() durchgeführt.
  • Ob ein String mit einem Wort beginnt oder endet, sagen die String-Methoden startsWith() und endsWith().
  • Zum Vergleichen von Teilen gibt es die String-Methode regionMatches(), eine Methode, die auch unabhängig von der Groß-/Kleinschreibung arbeiten kann.
  • Ist eine Übereinstimmung mit einem regulären Ausdruck gewünscht, helfen die Methode matches() von String sowie die speziellen Klassen Pattern und Matcher, die speziell für reguläre Ausdrücke sind.
Hinweis

Während die allermeisten Skript-Sprachen und auch C# Zeichenkettenvergleiche mit == erlauben, ist die Semantik für Java immer eindeutig: Der Vergleich mit == ist nur dann wahr, wenn die beiden Referenzen gleich sind, also zwei String-Objekte identisch sind; die Gleichheit reicht nicht aus.

Die Methode equals()

Die Klasse String überschreibt die aus der Klasse Object geerbte Methode equals(), um zwei Strings vergleichen zu können. Die Methode gibt true zurück, falls die Strings gleich lang sind und Zeichen für Zeichen übereinstimmen.

Beispiel

Bei dem Vergleich mit == ist das Ergebnis ein anderes als mit equals():

String input = javax.swing.JOptionPane.showInputDialog( "Passwort" );
System.out.println( input == "heinzelmann" ); // (1)
System.out.println( input.equals( "heinzelmann" ) ); // (2.1)
System.out.println( "heinzelmann".equals( input ) ); // (2.2)

Unter der Annahme, dass input die Zeichenkette »heinzelmann« referenziert, ergibt der Vergleich (1) über == den Wert false, da das von showInputDialog() gelieferte String-Objekt ein ganz anderes ist als das, was uns die virtuelle Maschine für den Test bereitstellt (später dazu mehr). Nur der equals()-Vergleich (2.1) und (2.2) ist hier korrekt, da hier die puren Zeichen verglichen werden, und die sind dann gleich.

Grundsätzlich sind Variante (2.1) und (2.2) gleich, da equals() symmetrisch ist. Doch gibt es einen Vorteil bei (2.2), denn da kann input auch null sein, und es gibt nicht wie bei (2.1) eine NullPointerException.

Hinweis

Beim equals()-Vergleich spielen alle Zeichen eine Rolle, auch wenn sie nicht sichtbar sind. So führen folgende Vergleiche zu false:

System.out.println( "\t".equals( "\n" ) );                // false
System.out.println( "\t".equals( "\t " ) ); // false
System.out.println( "\u0000".equals( "\u0000\u0000" ) ); // false

Die Methode equalsIgnoreCase()

equals() beachtet beim Vergleich die Groß- und Kleinschreibung. Mit equalsIgnoreCase() bietet die Java-Bibliothek eine zusätzliche Methode, um Zeichenketten ohne Beachtung der Groß-/Kleinschreibung zu vergleichen.

Beispiel

String str = "REISEPASS";
boolean result1 = str.equals( "Reisepass" ); // false
boolean result2 = str.equalsIgnoreCase( "ReISePaSs" ); // true

Eine kleine Anmerkung noch: Die Implementierung von equalsIgnoreCase() basiert intern darauf, beide Zeichenfolgen Zeichen für Zeichen abzulaufen, dabei einzelne Zeichen in Großbuchstaben zu konvertieren und dann zu prüfen, ob die beiden Großbuchstaben gleich sind.

Methodenvergleich

Der Vergleich "naß".toUpperCase().equals("NASS".toUpperCase()) beziehungsweise "NASS".toUpperCase().equals("naß".toUpperCase()) ergibt in beiden Fällen true. Doch "naß".equalsIgnoreCase("NASS") bzw. "NASS".equalsIgnoreCase("naß") ergeben false. Da Character.toUpperCase('ß') wieder ß ist, kann »naß« nicht »NASS« sein.

Lexikografische Vergleiche mit Größer/kleiner-Relation

Wie equals() und equalsIgnoreCase() vergleichen auch die Methoden compareTo(String) und compareToIgnoreCase(String) den aktuellen String mit einem anderen String. Nur ist der Rückgabewert von compareTo() kein boolean, sondern ein int. Das Ergebnis signalisiert, ob das Argument lexikografisch kleiner oder größer als das String-Objekt ist beziehungsweise mit diesem übereinstimmt. Das ist zum Beispiel in einer Sortiermethode wichtig. Der Sortieralgorithmus muss beim Vergleich zweier Strings wissen, wie sie einzusortieren sind.

Beispiel

Drei Strings in ihrer lexikografischen Ordnung. Alle Vergleiche ergeben true:

System.out.println( "Justus".compareTo( "Bob" )    > 0 );
System.out.println( "Justus".compareTo( "Justus" ) == 0 );
System.out.println( "Justus".compareTo( "Peter" ) < 0 );
Da im ersten Fall »Justus« lexikografisch größer ist als »Bob«, ist die numerische Rückgabe der Methode compareTo() größer 0.

Der von compareTo() vorgenommene Vergleich basiert nur auf der internen numerischen Kodierung der Unicode-Zeichen. Dabei berücksichtigt compareTo() nicht die landestypischen Besonderheiten, etwa die übliche Behandlung der deutschen Umlaute. Dafür müssten wir Collator-Klassen nutzen, die später in Abschnitt 4.12.1 vorgestellt werden.

compareToIgnoreCase() ist mit equalsIgnoreCase() vergleichbar, bei der die Groß-/Kleinschreibung keine Rolle spielt.

Hinweis

Das JDK implementiert compareToIgnoreCase() mit einem Comparator<String>, der zwei beliebige Zeichenketten in eine Reihenfolge bringt. Der Comparator<String> ist auch für uns zugänglich als statische Variable CASE_INSENSITIVE_ORDER. Er ist zum Beispiel praktisch für sortierte Mengen, bei denen die Groß-/Kleinschreibung keine Rolle spielt. Comparatoren werden genauer in Abschnitt 8.1, »Vergleichen von Objekten«, vorgestellt.

Endet der String mit ..., beginnt er mit ...?

Interessiert uns, ob der String mit einer bestimmten Zeichenfolge beginnt (wir wollen dies Präfix nennen), so rufen wir die startsWith()-Methode auf. Eine ähnliche Methode gibt es für Suffixe: endsWith(). Sie überprüft, ob ein String mit einer Zeichenfolge am Ende übereinstimmt.

Beispiel

Teste mit endsWith() eine Dateinamenendung und mit startsWith() eine Ansprache:

String   filename = "die besten stellungen (im schach).txt";
boolean isTxt = filename.endsWith( ".txt" ); // true
String email = "Sehr geehrte Frau Müller,\ndanke für Ihr Angebot.";
boolean isMale = email.startsWith( "Sehr geehrter Herr" ); // false

String-Teile mit regionMatches() vergleichen *

Eine Erweiterung der Ganz-oder-gar-nicht-Vergleichsmethoden bietet regionMatches(), die Teile einer Zeichenkette mit Teilen einer anderen vergleicht. Nimmt das erste Argument von regionMatches() den Wahrheitswert true an, dann spielt die Groß-/Kleinschreibung keine Rolle – damit lässt sich dann auch ein startsWith() und endsWith() mit Vergleichen unabhängig von der Groß-/Kleinschreibung durchführen. Der Rückgabewert ist wie bei equalsXXX() ein boolean.

Beispiel

Der Aufruf von regionMatches() ergibt true.

String s = "Deutsche Kinder sind zu dick";
boolean b = s.regionMatches( 9, "Bewegungsarmut bei Kindern", 19, 6 );

Die Methode beginnt den Vergleich am neunten Zeichen, also bei »K« im String s, und dem 19. Buchstaben in dem Vergleichsstring, ebenfalls ein »K«. Dabei beginnt die Zählung der Zeichen wieder bei 0. Ab diesen beiden Positionen werden sechs Zeichen verglichen. Im Beispiel ergibt der Vergleich von »Kinder« und »Kinder« dann true.

Beispiel

Sollte der Vergleich unabhängig von der Groß-/Kleinschreibung stattfinden, ist das erste Argument der überladenen Methode true:

String s = "Deutsche KINDER sind zu dick";
boolean b = s.regionMatches( true, 9, "Bewegungsarmut bei kindern", 19, 6 );


Galileo Computing - Zum Seitenanfang

4.4.8 Phonetische Vergleiche *Zur nächsten ÜberschriftZur vorigen Überschrift

Bei der equals()-Methode ist das Ergebnis nur dann true, wenn beide Zeichenketten absolut gleich sind, also jedes Zeichen »passt«. equalsIgnoreCase() ist schon etwas großzügiger, und hier sind etwa »vuvuzela« und »VuVuZeLa« gleich. Noch entspanntere Vergleiche erlauben Collator-Objekte, die etwa den Umlauten die Punkte nehmen, sodass »männo« und »manno« dann gleich sind.

Vergleiche aufgrund von Ähnlichkeiten und gleichem »Klang« gibt es jedoch in der Java-Standardbibliothek nicht. Das ist aber bei Namen interessant. Mein Name »Ullenboom« wird oft zu »Uhlenbohm« umgebaut, was sich im Prinzip gleich anhört (und jeder Maier, Meyer, Mayer, Meir, Myer, Meier kennt das Problem).

Zur Erkennung helfen besondere String-Algorithmen weiter. Für (insbesondere englische) Namen sind der Soundex-Algorithmus und seine Verbesserungen (Double) Metaphone entwickelt worden. Wer eine Realisierung in Java sucht, der findet bei Apache Commons Codec (http://commons.apache.org/codec/userguide.html) passende Implementierungen. So liefert etwa isDoubleMetaphoneEqual(String value1, String value2) einen Wahrheitswert, der aussagt, ob die Strings ähnlich sind. Interessant sind die Algorithmen auch für Korrekturhilfen.[117](Wobei ich die Korrekturvorschläge »Ullendorf«, »Quellenbox«, »Patrouillenboot« und »Müllenborn « in den Textboxen von Google Chrome schon sehr schräg finde.) Der Name "Ullenboom" ist dem Soundex-Code U451 zugeordnet. Schreibt jemand diesen Namen falsch, etwa "Uhlenbohm", und ist dieser Name nicht im Wörterbuch, so berechnet das Programm von "Uhlenbohm" ebenfalls den Soundex und kommt auf U451. Ein Blick in die Datenstruktur bei U451 liefert dann den korrekten Namen "Ullenboom" oder andere Vorschläge, die den gleichen Soundex ergeben.

Wie ähnlich denn nun Strings sind, sagen andere Algorithmen. Die Levenshtein-Distanz zum Beispiel berechnet sich aus der (kleinstnötigen) Anzahl der einzufügenden, zu löschenden oder zu ersetzenden Zeichen, um von einem String zum anderen zu kommen; daher nennt sie sich auch Edit-Distanz. Von "Chris" nach "Char" ist die Edit-Distanz drei und von "Ullenboom" nach "Uhlenbohm" vier. Jaro-Winkler ist ein weiter Algorithmus, der die Ähnlichkeit zwischen 0 und 1 angibt. Das Projekt SecondString (http://secondstring.sourceforge.net/) implementiert diese Algorithmen – und noch ein Dutzend mehr.


Galileo Computing - Zum Seitenanfang

4.4.9 String-Teile extrahierenZur nächsten ÜberschriftZur vorigen Überschrift

Die wichtigste Methode charAt(int index) der Klasse String haben wir schon mehrfach benutzt. Sie ist aber nicht die einzige Methode, um auf gewisse Teile eine Strings zuzugreifen.

Teile eines Strings als String mit substring() erfragen

Wollen wir einen Teilstring aus der Zeichenkette erfragen, so greifen wir zur Methode substring(). Sie existiert in zwei Varianten – beide liefern ein neues String-Objekt zurück, das dem gewünschten Ausschnitt des Originals entspricht.

Beispiel

substring(int) liefert eine Teilzeichenkette ab einem Index bis zum Ende. Das Ergebnis ist ein neues String-Objekt:

String s1 = "Infiltration durch Penetration";
// Position: 19
String s2 = s1.substring( 19 ); // Penetration

Der Index von substring(int) gibt die Startposition (null-basiert) an, ab der Zeichen in die neue Teilzeichenkette kopiert werden. substring(int) liefert den Teil von diesem Zeichen bis zum Ende des ursprünglichen Strings – es ergibt s.substring(0) gleich s.

Wollen wir die Teilzeichenkette genauer spezifizieren, so nutzen wir die zweite Variante, substring(int, int). Ihre Argumente geben den Anfang und das Ende des gewünschten Ausschnitts an.

Beispiel

Schneide einen Teil des Strings aus:

String tear = "'Jede Träne kitzelt auch die Wange.'";
// 0 6 11
System.out.println( tear.substring( 6, 11 ) ); // Träne
Während die Startposition inklusiv ist, ist die Endposition exklusiv. Das heißt, bei der Endposition gehört das Zeichen nicht mehr zur Teilzeichenkette.

Die Methode substring(int) ist nichts anderes als eine Spezialisierung von substring(int, int), denn die erste Variante mit dem Startindex lässt sich auch als s.substring(beginIndex, s.length()) schreiben.

Selbstverständlich kommen nun diverse Indexüberprüfungen hinzu – eine StringIndexOutOfBoundsException meldet fehlerhafte Positionsangaben wie bei charAt().

String vor/nach einem Trennstring *

Ist ein Trennzeichen gegeben und ein Teilstring vor oder nach diesem Trennzeichnen gewünscht, bietet die String-Klasse keine Bibliotheksmethode an.[118](Selbst XPath bietet mit substring-before() und substring-after() solche Funktionen. Und Apache Commons Lang (http://commons.apache.org/lang/) bildet sie auch nach in der Klasse org.apache.commons.lang.StringUtils.) Dabei wäre eine solche Methode praktisch, etwa bei Dateien, bei denen der Punkt den Dateinamen vom Suffix trennt. Wir wollen zwei statische Utility-Methoden, substringBefore(String string, String delimiter) und substringAfter(String string, String delimiter), schreiben, die genau diese Aufgabe übernehmen. Angewendet sehen sie dann so aus (wir ignorieren für einen Moment, dass der Dateiname selbst auch einen Punkt enthalten kann):

  • substringBefore( "index.html", "." ) R "index"
  • substringAfter( "index.html", "." ) R "html"

Die Implementierung der Methoden ist einfach: Im ersten Schritt suchen die Methoden mit indexOf() nach dem Trenner. Anschließend liefern sie mit substring() den Teilstring vor bzw. hinter diesem gefundenen Trennstring. Noch einige Vereinbarungen: Der Trenner ist kein Teil der Rückgabe. Und taucht das Trennzeichen nicht im String auf, ist die Rückgabe von substringBefore() der gesamte String und bei substringAfter() der Leerstring. String und Trenner dürfen nicht null sein. Wenn dem so ist, folgt eine NullPointerException und zeigt so den Programmierfehler an. Ausprogrammiert sehen die beiden Methoden so aus:

Listing 4.7: StringUtils.java

public class StringUtils
{
/**
* Returns the substring before the first occurrence of a delimiter. The
* delimiter is not part of the result.
*
* @param string String to get a substring from.
* @param delimiter String to search for.
* @return Substring before the first occurrence of the delimiter.
*/
public static String substringBefore( String string, String delimiter )
{
int pos = string.indexOf( delimiter );

return pos >= 0 ? string.substring( 0, pos ) : string;
}

/**
* Returns the substring after the first occurrence of a delimiter. The
* delimiter is not part of the result.
*
* @param string String to get a substring from.
* @param delimiter String to search for.
* @return Substring after the last occurrence of the delimiter.
*/
public static String substringAfter( String string, String delimiter )
{
int pos = string.indexOf( delimiter );

return pos >= 0 ? string.substring( pos + delimiter.length() ) : "";
}
}

Zur Übung sei es den Lesern überlassen, noch die zwei Methoden substringBeforeLast() und substringAfterLast() zu realisieren, die statt indexOf() die Methode lastIndexOf() einsetzen (mit den beiden Methoden kann auch der Dateiname selbst einen Punkt enthalten). Frage: Lässt sich in der Implementierung einfach indexOf() durch lastIndexOf() ersetzen, und das war es dann schon?

Mit getChars() Zeichenfolgen als Array aus dem String extrahieren *

Während charAt() nur ein Zeichen liefert, kopiert getChars() mehrere Zeichen aus einem angegebenen Bereich des Strings in ein übergebenes Feld.

Beispiel

Kopiere Teile des Strings in ein Feld:

String s = "Blasiussegen";
char[] chars = new char[ 5 ];
int srcBegin = 7;
s.getChars( srcBegin, srcBegin + 5, chars, 0 );
System.out.println( new String(chars) ); // segen
s.getChars() kopiert ab Position 7 aus dem String s fünf Zeichen in die Elemente des Arrays chars. Das erste Zeichen aus dem Ausschnitt steht dann in chars[0].

Die Methode getChars() muss natürlich wieder testen, ob die gegebenen Argumente im grünen Bereich liegen, das heißt, ob der Startwert nicht < 0 ist und ob der Endwert nicht über die Größe des Strings hinausgeht. Passt das nicht, löst die Methode eine StringIndexOutOfBoundsException aus. Liegt zudem der Startwert hinter dem Endwert, gibt es ebenfalls eine StringIndexOutOfBoundsException, die anzeigt, wie groß die Differenz der Positionen ist. Am besten ist es, die Endposition aus der Startposition zu berechnen, wie es im obigen Beispiel geschehen ist. Passen alle Zeichen in das Feld, kopiert die Implementierung der Methode getChars() mittels System.arraycopy() die Zeichen aus dem internen Array des String-Objekts in das von uns angegebene Ziel.

Möchten wir den kompletten Inhalt eines Strings als ein Array von Zeichen haben, so können wir die Methode toCharArray() verwenden. Intern arbeitet die Methode auch mit getChars(). Als Ziel-Array legt toCharArray() nur ein neues Array an, das wir dann zurückbekommen.

Hinweis

Mit folgendem Idiom lässt sich über eine Zeichenkette iterieren:

String string = "Herr, schmeiß Java vom Himmel!";
for ( char c : string.toCharArray() ) {
System.out.println( c );
}
Diese Lösung hat aber ihren Preis, denn ein neues char[]-Objekt einfach für den Durchlauf zu erzeugen, kostet Speicher und Rechenzeit für die Speicherbereitstellung und die -bereinigung. Daher ist diese Variante nicht empfehlenswert. Hübscher wäre es natürlich, wenn rechts vom Doppelpunkt automatisch ein String-Objekt berücksichtigt werden würde und den Compiler dazu anregte, die Zeichenkette zu durchlaufen.


Galileo Computing - Zum Seitenanfang

4.4.10 Strings anhängen, Groß-/Kleinschreibung und LeerraumZur nächsten ÜberschriftZur vorigen Überschrift

Obwohl String-Objekte selbst unveränderlich sind, bietet die Klasse String Methoden an, die aus einer Zeichenkette Teile herausnehmen oder ihr Teile hinzufügen. Diese Änderungen werden natürlich nicht am String-Objekt selbst vorgenommen, auf dem die Methode aufgerufen wird, sondern die Methode liefert eine Referenz auf ein neues String-Objekt mit verändertem Inhalt zurück.

Anhängen an Strings

Eine weitere Methode erlaubt das Anhängen von Teilen an einen String. Wir haben dies schon öfter mit dem Plus-Operator realisiert. Die Methode der String-Klasse dazu heißt concat(String). Wir werden später sehen, dass es die StringBuilder/StringBuffer-Klassen noch weiter treiben und eine überladene Methode append() mit der gleichen Funktionalität anbieten. Das steckt auch hinter dem Plus-Operator. Der Compiler wandelt dies automatisch in eine Kette von append()-Aufrufen um.

Beispiel

Hänge das aktuelle Tagesdatum hinter eine Zeichenkette:

String s1 = "Das aktuelle Datum ist: ";
String s2 = new Date().toString();
String s3 = s1.concat( s2 ); // Das aktuelle Datum ist:
// Tue Sep 01 14:46:41 CEST 2011

Die concat()-Methode arbeitet relativ zügig und effizienter als der Plus-Operator, der einen temporären String-Puffer anlegt. Doch mit dem Plus-Operator ist es hübscher anzusehen (doch wie das so ist: Sieht nett aus, aber ...).

Beispiel

Ähnlich wie im obigen Beispiel können wir Folgendes schreiben:

String s3 = "Das aktuelle Datum ist: " + new Date().toString();
Es geht sogar noch kürzer, denn der Plus-Operator ruft automatisch toString() bei Objekten auf:
String s3 = "Das aktuelle Datum ist: " + new Date();

concat() legt ein internes Feld an, kopiert die beiden Zeichenreihen per getChars() hinein und liefert mit einem String-Konstruktor die resultierende Zeichenkette.

Groß-/Kleinschreibung

Die Klasse Character deklariert einige statische Methoden, um einzelne Zeichen in Groß-/3Kleinbuchstaben umzuwandeln. Die Schleife, die das für jedes Zeichen übernimmt, können wir uns sparen, denn dazu gibt es die Methoden toUpperCase() und toLowerCase() in der Klasse String. Interessant ist an beiden Methoden, dass sie einige sprachabhängige Feinheiten beachten. So zum Beispiel, dass es im Deutschen nicht wirklich ein großes »ß« gibt, denn »ß« wird zu »SS«. Gammelige Textverarbeitungen bekommen das manchmal nicht auf die Reihe, und im Inhaltsverzeichnis steht dann so etwas wie »SPAß IN DER NAßZELLE«. Aber bei möglichen Missverständnissen müsste »ß« auch zu »SZ« werden, vergleiche »SPASS IN MASZEN« mit »SPASS IN MASSEN« (ein ähnliches Beispiel steht im Duden). Diese Umwandlung ist aber nur von Klein nach Groß von Bedeutung. Für beide Konvertierungsrichtungen gibt es jedoch im Türkischen Spezialfälle, bei denen die Zuordnung zwischen Groß- und Kleinbuchstaben von der Festlegung in anderen Sprachen abweicht.

Beispiel

Konvertierung von Groß- in Kleinbuchstaben und umgekehrt:

String s1 = "Spaß in der Naßzelle.";
String s2 = s1.toLowerCase().toUpperCase();
System.out.println( s2 ); // SPASS IN DER NASSZELLE.
System.out.println( s2.length() – s1.length() ); // 2

Das Beispiel dient zugleich als Warnung, dass sich im Fall von »ß« die Länge der Zeichenkette vergrößert. Das kann zu Problemen führen, wenn vorher Speicherplatz bereitgestellt wurde. Dann könnte die neue Zeichenkette nicht mehr in den Speicherbereich passen. Arbeiten wir nur mit String-Objekten, haben wir dieses Problem glücklicherweise nicht. Aber berechnen wir etwa für einen Texteditor die Darstellungsbreite einer Zeichenkette in Pixel auf diese Weise, dann sind Fehler vorprogrammiert.

Um länderspezifische Besonderheiten zu berücksichtigen, lassen sich die toXXXCase()-Methoden zusätzlich mit einem Locale-Objekt füttern (Locale-Objekte repräsentieren eine sprachliche Region).

Hinweis

Es gibt Konvertierungen in Groß-/Kleinbuchstaben, die abhängig von der Landessprache zu unterschiedlichen Zeichenfolgen führen. Die Angabe eines Locale bei den beiden toXXXXXCase()-Methoden ist insbesondere bei türkischsprachigen Applikationen wichtig:

System.out.println( "TITANIK".toLowerCase()  );                     // titanik
System.out.println( "TITANIK".toLowerCase(new Locale( "tr" ) ) ); // tıtanık
Kleiner Unterschied: Im zweiten Ergebnisstring hat das i keinen i-Punkt!


Hinweis

Die parameterlosen Methoden toUpperCase() und toLowerCase() wählen die Sprachumgebung gemäß den Länder-Einstellungen des Betriebssystems aus. Am Beispiel toLowerCase():

public String toLowerCase() {
return toLowerCase( Locale.getDefault() );
}
Die Voreinstellung muss nicht die beste sein.

Leerraum entfernen

In einer Benutzereingabe oder Konfigurationsdatei steht nicht selten vor oder hinter dem wichtigen Teil eines Texts Leerraum wie Leerzeichen oder Tabulatoren. Vor der Bearbeitung sollten sie entfernt werden. Die String-Klasse bietet dazu trim() an.

Beispiel

Entferne Leer- und ähnliche Füllzeichen am Anfang und Ende eines Strings:

String s = " \tSprich zu der Hand.\n  \t ";
System.out.println( "'" + s.trim() + "'" ); // 'Sprich zu der Hand.'


Beispiel

Teste, ob ein String mit Abzug allen Weißraums leer ist:

boolean isBlank = "".equals( s.trim() );
Alternativ:
boolean isBlank = s.trim().isEmpty();

Andere Modesprachen wie Visual Basic bieten dazu noch trim()-Methoden an, die nur die Leerzeichen vorher oder nachher verwerfen. Die Java-Bibliothek bietet das so einfach nicht.


Galileo Computing - Zum Seitenanfang

4.4.11 Suchen und ersetzenZur nächsten ÜberschriftZur vorigen Überschrift

Da String-Objekte unveränderlich sind, kann eine Veränderungsmethode nur einen neuen String mit den Veränderungen zurückgeben. Wir finden in Java vier Methoden, die suchen und ersetzen:

final class java.lang.String
implements Serializable, Comparable<String>, CharSequence
  • String replace(char oldChar, char newChar). Ersetzt einzelne Zeichen.
  • String replace(CharSequence target, CharSequence replacement). Ersetzt eine Zeichenkette durch eine andere Zeichenkette.
  • String replaceAll(String regex, String replacement). Ersetzt alle Strings, die durch einen regulären Ausdruck beschrieben werden.
  • String replaceFirst(String regex, String replacement). Ersetzt den ersten String, der durch einen regulären Ausdruck beschrieben wird.

Ersetzen ohne reguläre Ausdrücke

Die replace(char, char)-Methode ersetzt einzelne Zeichen.

Beispiel

Ändere den in einer Zeichenkette vorkommenden Buchstaben »o« in »u«:

String s1 = "Honolulu";
String s2 = s1.replace( 'o', 'u' ); // s2 = "Hunululu"
Das String-Objekt mit dem Namen s1 wird selbst nicht verändert. Es wird nur ein neues String-Objekt mit dem Inhalt »Hunululu« erzeugt und von replace() zurückgegeben.

Gibt es etwas zu ersetzen, erzeugt replace() intern ein neues Zeichenfeld, führt die Ersetzungen durch und konvertiert das interne Zeichenfeld in ein String-Objekt, was die Rückgabe ist. Gab es nichts zu ersetzen, bekommen wir das gleiche String-Objekt zurück, das die Anfrage stellte. Die replace()-Methode ersetzt immer alle Zeichen. Eine Variante, die nur das erste Zeichen ersetzt, müssen wir uns selbst schreiben.

Eine zweite überladene Variante, replace(CharSequence, CharSequence), sucht nach allen auftretenden Zeichenfolgen und ersetzt sie durch eine andere Zeichenfolge. Der Ersetzungsstring kann auch leer sein.

Beispiel

Im String s soll »Schnecke« durch »Katze« ersetzt werden:

String s =
"Schnecken erschrecken, wenn Schnecken an Schnecken schlecken, " +
"weil zum Schrecken vieler Schnecken Schnecken nicht schmecken.";
System.out.println( s.replace("Schnecke", "Katze") );
Das Ergebnis auf dem Bildschirm ist: »Katzen erschrecken, wenn Katzen an Katzen schlecken, weil zum Schrecken vieler Katzen Katzen nicht schmecken.«

Suchen und ersetzen mit regulären Ausdrücken

Die Methoden replaceAll() und replaceFirst() suchen in Zeichenketten mithilfe von regulären Ausdrücken (mehr dazu folgt in Abschnitt 4.8, »Reguläre Ausdrücke«) und nehmen Ersetzungen vor; replaceFirst() ersetzt, wie der Name schon sagt, nur das erste Auftreten.

Beispiel

Mehr als zwei Leerzeichen in Folge sollen auf ein Leerzeichen komprimiert werden:

String s = "Alles  fit im   Schritt?";
System.out.println( s.replaceAll( " +", " " ) ); // Alles fit im Schritt?
System.out.println( s.replaceFirst( " +", " " ) ); // Alles fit im Schritt?

Weil der Suchstring immer ein regulärer Ausdruck ist und Sonderzeichen wie ».« oder »+« eine Sonderrolle einnehmen, eignen sich replaceAll() und replaceFirst() nicht direkt für allgemeine Ersetzungsaufgaben; hier ist die replace()-Methode passender.

Beispiel

Für eine String-Ersetzung stellen wir replace() und replaceAll() nebeneinander:

String s =
"'Tag, Karl.' 'Wie geht's, Karl?' 'Gut, Karl.' 'Kahl, Karl?' 'Ja, Karl, ganz kahl.'";
System.out.println( s.replace( ".", "!" ) );
Der Aufruf ersetzt alle Punkte durch Ausrufezeichen, sodass das Ergebnis wie folgt lautet:
'Tag, Karl!' 'Wie geht's, Karl?' 'Gut, Karl!' 'Kahl, Karl?' 'Ja, Karl, ganz kahl!'
Nutzen wir s.replaceAll(".", "!"), führt das nicht zum Erfolg, sondern nur zu der Zeichenkette:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Der Punkt steht in regulären Ausdrücken für beliebige Zeichen. Erst, wenn er mit \\ ausmaskiert wird – wegen des Sonderstatus von »\« muss auch dieses Zeichen selbst ausmaskiert werden –, liefert die Anweisung wie in s.replaceAll("\\.", "!") das gewünschte Ergebnis. Die statische Methode Pattern.quote(String) maskiert die Pattern-Sonderzeichen für uns aus, sodass auch s.replaceAll(Pattern.quote("."), "!") gut funktioniert. Zur Klasse java.util.regex.Pattern und regulären Ausdrücken folgt mit Abschnitt 4.8 ein eigenes großes Unterkapitel.


Galileo Computing - Zum Seitenanfang

4.4.12 String-Objekte mit Konstruktoren neu anlegen *Zur nächsten ÜberschriftZur vorigen Überschrift

Liegt die Zeichenkette nicht als String-Literal vor, lassen sich mit den unterschiedlichen Konstruktoren der String-Klasse neue String-Objekte aufbauen. Die meisten Konstruktoren sind für Spezialfälle gedacht und kommen in normalen Java-Programmen nicht vor:

final class java.lang.String
implements CharSequence, Comparable<String>, Serializable
  • String()
    Erzeugt ein neues Objekt ohne Zeichen (den leeren String "").
  • String(String string)
    Erzeugt ein neues Objekt mit einer Kopie von string. Es wird selten benötigt, da String-Objekte unveränderbar (immutable) sind.
  • String(char[] value)
    Erzeugt ein neues Objekt und kopiert die im char-Feld vorhandenen Zeichen in das neue String-Objekt.
  • String(char[] value, int offset, int length)
    Erzeugt wie String(char[]) einen String aus einem Ausschnitt eines Zeichenfelds. Der verwendete Ausschnitt beginnt bei dem Index offset und umfasst length Zeichen.
  • String(byte[] bytes)
    Erzeugt ein neues Objekt aus dem Byte-Feld. Das byte-Array enthält keine Unicode-Zeichen, sondern eine Folge von Bytes, die nach der Standardkodierung der jeweiligen Plattform in Zeichen umgewandelt werden.
  • String(byte[] bytes, int offset, int length)
    Erzeugt wie String(byte[]) einen String aus einem Ausschnitt eines Byte-Felds.
  • String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
    Erzeugt einen neuen String von einem Byte-Array mithilfe einer speziellen Zeichenkodierung, die die Umwandlung von Bytes in Unicode-Zeichen festlegt.
  • String(byte[] bytes, int offset, int length, String charset)
    throws UnsupportedEncodingException
    Erzeugt einen neuen String mit einem Teil des Byte-Arrays mithilfe einer speziellen Zeichenkodierung.
  • String(StringBuffer buffer)
  • String(StringBuilder builder)
    Erzeugt aus einem veränderlichen StringBuffer/StringBuilder-Objekt ein unveränderliches String-Objekt, das dieselbe Zeichenreihe repräsentiert.
  • String(int[] codePoints, int offset, int count)
    Erzeugt ein String-Objekt mit Unicode-Codepoints, die Zeichen über int kodieren.

Die Konstruktoren sind im Speziellen nur dann nötig, wenn aus einer Fremdrepräsentation wie einem StringBuilder, StringBuffer, char[] oder byte[] oder Teilen von ihnen ein String-Objekt aufgebaut werden soll.

Beispiel

Erzeuge einen String einer gegebenen Länge:

public static String generateStringWithLength( int len, char fill )
{
if ( len < 0 )
return null;

char[] cs = new char[ len ];
Arrays.fill( cs, fill );
return new String( cs );
}
In der String-Klasse gibt es keine Methode, die eine Zeichenkette einer vorgegebenen Länge aus einem einzelnen Zeichen erzeugt.

Beispiel

Teste, ob zwei Zeichenketten Anagramme darstellen, also Zeichenfolgen, die beim Vertauschen von Buchstaben gleich sind:

String a1 = "iPad", a2 = "Paid";
char[] a1chars = a1.toCharArray();
char[] a2chars = a2.toCharArray();
Arrays.sort( a1chars );
Arrays.sort( a2chars );
boolean isAnangram = new String(a1chars).equalsIgnoreCase(new String(a2chars));
System.out.println( isAnangram ); // true
Die Methode Arrays.sort() haben wir bisher noch nicht kennengelernt, aber ihr Sinn ergibt sich intuitiv: Die Methode sortiert ein Feld, in dem Fall das char-Feld.

Über den Konstruktoraufruf new String(String)

Ein Konstruktor führt leicht zur Verwirrung, und zwar der Konstruktor, der einen anderen String annimmt. So ergeben die beiden folgenden Zeilen die Referenz auf ein String-Objekt:

String rudi = "There is no spoon";
String rudi = new String( "There is no spoon" );

Die zweite Lösung erzeugt unnötigerweise ein zusätzliches String-Objekt, denn das Literal ist ja schon ein vollwertiges String-Objekt.

Tuning-Hinweis

Der Konstruktor ist nur für eine besondere Optimierung zu gebrauchen, die in der Regel wie folgt aussieht (sei s ein großer String und t ein Teilstring):

String s = ...
String t = new String( s.substring(...) );
Die String-Klasse in Java ist nichts anderes als eine Abstraktion von einem darunterliegenden char-Feld.[119](Die ersten Zeilen in der Klasse String beginnen mit:
public final class String implements java.io.Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final char value[];
)
Bei einem substring() wird kein neues char-Feld mit der Teilzeichenkette aufgebaut, sondern es wird das ursprüngliche char-Feld (in unserem Beispiel von s) genutzt, und es werden lediglich die Start- und End-Positionen gesetzt. Ein String-Objekt enthält daher nicht nur intern ein Attribut für das char-Feld, sondern auch noch offset (also den Startpunkt) und die Länge.[120](
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
)
Somit ist die Operation substring() sehr performant, da keine Zeichenfelder kopiert werden müssen. Das Problem: Ist das von s referenzierte char-Feld sehr groß, wird dieses Feld ebenfalls vom Teilstring referenziert. Wenn das ursprüngliche String-Objekt s vom GC entfernt wird, bleibt trotzdem das große char-Feld bestehen, denn es wird vom Teilstring referenziert. Um den Speicherbedarf in diesem Fall zu optimieren, ist der new String(String)-Konstruktor geeignet, denn er legt ein neues kompaktes char-Feld an, das ausschließlich die Zeichen speichert.

Somit ist kein Verweis mehr auf das ursprüngliche Feld vorhanden, was dann der GC wegräumen kann, wenn das ursprüngliche String-Objekt s auch nicht mehr existiert.

Strings im Konstantenpool

Die JVM erzeugt für jedes Zeichenketten-Literal automatisch ein entsprechendes String-Objekt. Das geschieht für jede konstante Zeichenkette höchstens einmal, egal wie oft sie im Programmverlauf benutzt wird und welche Klassen den String nutzen. Dieses String-Objekt »lebt« in einem Bereich, der Konstantenpool genannt wird.[121](Die Java-Bibliothek implementiert hier das Entwurfsmuster Fliegengewicht (Flyweight-Pattern) der Gang of Four.)

Hinweis

Nehmen wir an, die Anweisung

System.out.println( "tutego" );
steht in einer Klasse A und in einer anderen Klasse B steht:
int len = "tutego".length();
Dann gibt es die Zeichenfolge »tutego« als String-Objekt nur ein einziges Mal in der Laufzeitumgebung.

Bei konstanten Werten führt der Compiler Optimierungen durch, etwa in der Art, dass er konstante Ausdrücke gleich berechnet. Nicht nur setzt er für Ausdrücke wie 1 + 2 das Ergebnis 3 ein, auch aufgebrochene konstante String-Teile, die mit Plus konkateniert werden, fügt der Compiler zu einer Zeichenkette zusammen.

Beispiel

Die erste und zweite Deklaration sehen im Bytecode gleich aus:

String s =
"Operating systems are like underwear – nobody really wants to look at them.";
String s = "Operating systems are like underwear " +
'-' + " nobody really wants to look at them.";

Der Compiler fügt die Zeichenketten[122](Das Zitat stammt übrigens von Bill Joy (eigentlich heißt er William Nelson Joy), der Sun Microsystems mit gegründet hat. Er war an der Entwicklung einer beeindruckenden Anzahl von Tools und Technologien beteiligt, wie dem Unix-Kernel, TCP/IP (»Edison of the Internet«), dem Dateisystem NFS, Java, SPARC-Prozessoren, dem vi-Editor usw.) automatisch zu einer großen Zeichenkette zusammen, sodass keine Konkatenation zur Laufzeit nötig ist.

Leerer String, Leer-String oder Null-String

Die Anweisungen

String s = "";

und

String s = new String();

referenzieren in beiden Fällen String-Objekte, die keine Zeichen enthalten. Die zweite Schreibweise erzeugt aber ein neues String-Objekt, während im ersten Fall das String-Literal im Konstantenpool liegt.

Ein String ohne Zeichen nennen wir leeren String, Leer-String oder Null-String. Der letzte Begriff ist leider etwas unglücklich gewählt, sodass wir ihn im Buch nicht nutzen, denn der Begriff Null-String kann leicht mit dem Begriff null-Referenz verwechselt werden. Doch während Zugriffe auf einem Null-String unproblematisch sind, führen Dereferezierungen auf der null-Referenz unweigerlich zu einer NullPointerException:

String s = null;
System.out.println( s ); // Ausgabe: null
s.length(); // Fehler NullPointerException

printXXX(null) führt zu der Konsolenausgabe »null« und zu keiner Ausnahme, da es eine Fallunterscheidung in printXXX() gibt, die die null-Referenz als Sonderfall betrachtet.[123](In der Implementierung von PrintStream von Sun: public void print( String s ) { if ( s == null ) s = "null"; write( s ); }) Der Zugriff auf s über s.length() führt dagegen zur unbeliebten NullPointerException.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück
  Zum Katalog
Zum Katalog: Java ist auch eine Insel





Java ist auch eine Insel
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java 7 – Mehr als eine Insel





 Java 7 –
 Mehr als eine Insel


Zum Katalog: Android 3






 Android 3


Zum Katalog: Android-Apps entwickeln






 Android-Apps
 entwickeln


Zum Katalog: NetBeans Platform 7






 NetBeans
 Platform 7


Zum Katalog: Einstieg in Eclipse 3.7






 Einstieg in
 Eclipse 3.7


Zum Katalog: Einstieg in Java






 Einstieg
 in Java


Zum Katalog: Einstieg in Java 7






 Einstieg in
 Java 7


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2011
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de