Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil5 Der Umgang mit Zeichenketten
Pfeil5.1 Von ASCII über ISO-8859-1 zu Unicode
Pfeil5.1.1 ASCII
Pfeil5.1.2 ISO/IEC 8859-1
Pfeil5.1.3 Unicode
Pfeil5.1.4 Unicode-Zeichenkodierung
Pfeil5.1.5 Escape-Sequenzen/Fluchtsymbole
Pfeil5.1.6 Schreibweise für Unicode-Zeichen und Unicode-Escapes
Pfeil5.1.7 Java-Versionen gehen mit dem Unicode-Standard Hand in Hand *
Pfeil5.2 Datentypen für Zeichen und Zeichenfolgen
Pfeil5.3 Die Character-Klasse
Pfeil5.3.1 Ist das so?
Pfeil5.3.2 Zeichen in Großbuchstaben/Kleinbuchstaben konvertieren
Pfeil5.3.3 Vom Zeichen zum String
Pfeil5.3.4 Von char in int: vom Zeichen zur Zahl *
Pfeil5.4 Zeichenfolgen
Pfeil5.5 Die Klasse String und ihre Methoden
Pfeil5.5.1 String-Literale als String-Objekte für konstante Zeichenketten
Pfeil5.5.2 Konkatenation mit +
Pfeil5.5.3 String-Länge und Test auf Leer-String
Pfeil5.5.4 Zugriff auf ein bestimmtes Zeichen mit charAt(int)
Pfeil5.5.5 Nach enthaltenen Zeichen und Zeichenfolgen suchen
Pfeil5.5.6 Das Hangman-Spiel
Pfeil5.5.7 Gut, dass wir verglichen haben
Pfeil5.5.8 String-Teile extrahieren
Pfeil5.5.9 Strings anhängen, zusammenfügen, Groß-/Kleinschreibung und Weißraum
Pfeil5.5.10 Gesucht, gefunden, ersetzt
Pfeil5.5.11 String-Objekte mit Konstruktoren und aus Wiederholungen erzeugen *
Pfeil5.6 Veränderbare Zeichenketten mit StringBuilder und StringBuffer
Pfeil5.6.1 Anlegen von StringBuilder-Objekten
Pfeil5.6.2 StringBuilder in andere Zeichenkettenformate konvertieren
Pfeil5.6.3 Zeichen(folgen) erfragen
Pfeil5.6.4 Daten anhängen
Pfeil5.6.5 Zeichen(folgen) setzen, löschen und umdrehen
Pfeil5.6.6 Länge und Kapazität eines StringBuilder-Objekts *
Pfeil5.6.7 Vergleich von StringBuilder-Exemplaren und Strings mit StringBuilder
Pfeil5.6.8 hashCode() bei StringBuilder *
Pfeil5.7 CharSequence als Basistyp
Pfeil5.8 Konvertieren zwischen Primitiven und Strings
Pfeil5.8.1 Unterschiedliche Typen in String-Repräsentationen konvertieren
Pfeil5.8.2 String-Inhalt in einen primitiven Wert konvertieren
Pfeil5.8.3 String-Repräsentation im Format Binär, Hex und Oktal *
Pfeil5.8.4 parseXXX(…)- und printXXX()-Methoden in DatatypeConverter *
Pfeil5.9 Strings zusammenhängen (konkatenieren)
Pfeil5.9.1 Strings mit StringJoiner zusammenhängen
Pfeil5.10 Zerlegen von Zeichenketten
Pfeil5.10.1 Splitten von Zeichenketten mit split(…)
Pfeil5.10.2 Yes we can, yes we scan – die Klasse Scanner
Pfeil5.11 Ausgaben formatieren
Pfeil5.11.1 Formatieren und Ausgeben mit format()
Pfeil5.12 Zum Weiterlesen
 

Zum Seitenanfang

5.5    Die Klasse String und ihre Methoden Zur vorigen ÜberschriftZur nächsten Überschrift

Die Entwickler von Java haben eine Symbiose zwischen String als Klasse und dem String als eingebauten Datentyp gebildet. 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), wie "Hallo Welt".

  • Die Konkatenation (Aneinanderreihung von Strings mit +, wie in "Hallo" + " " + "Welt") 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; benutzerdefinierte überladene Operatoren sind in Java nicht möglich.

Die Klasse String bietet genau eine Konstante CASE_INSENSITIVE_ORDER vom Typ Comparator<String>, ein paar Konstruktoren, statische Methoden sowie Objektmethoden – diese schauen wir uns im Folgenden an.

 

Zum Seitenanfang

5.5.1    String-Literale als String-Objekte für konstante Zeichenketten Zur vorigen ÜberschriftZur nächsten Ü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.

[zB]  Beispiel

"Charlie Hebdo".length() liefert die Länge der Zeichenkette. Das Ergebnis ist 13. Weißraum 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.

 

Zum Seitenanfang

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

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

[zB]  Beispiel

Haben die Glieder bei der Konkatenation unterschiedliche Datentypen und ist einer String, werden alle folgenden automatisch auf String gebracht:

int    age    = 39;

double height = 1.83;

String s = "Alter: " + age + ", Größe: " + height;

System.out.println( s ); // Alter: 39, Größe: 1.83
 

Zum Seitenanfang

5.5.3    String-Länge und Test auf Leer-String Zur vorigen ÜberschriftZur nächsten Ü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 sie so implementiert, dass sie die Anzahl der Zeichen im String (die Länge des Strings) zurückgibt. Um herauszufinden, ob der String keine Zeichen hat, lässt sich neben length() == 0 auch die Methode isEmpty() nutzen. In Java 11 ist die Methode isBlank() hinzugekommen, die testet, ob der String leer ist oder nur aus Weißraum besteht. Weißraum ist jedes Zeichen, bei dem Character.isWhitespace(int) wahr anzeigt.

Anweisung

Ergebnis

"".length()

0

"".isEmpty()

true

" ".length()

1

" ".isEmpty()

false

" ".isBlank()

true

String s = null; s.length();

NullPointerException

Tabelle 5.7    Ergebnisse der Methoden »length()«, »isEmpty()« und »isBlank()«

[»]  Hinweis

Bei Arrays sind wir daran gewöhnt, dass length ein Attribut ist. Bei String ist length() eine Methode, benötigt also ein paar runde Klammern.

Eine praktische Hilfsmethode: isNullOrEmpty(String)

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

Listing 5.3    src/main/java/com/tutego/insel/string/LengthAndEmptyDemo.java, Ausschnitt

/**

* Checks if a String is {@code null} or empty ({@code ""}).

*

* <pre>

* StringUtils.isNullOrEmpty(null) == true

* StringUtils.isNullOrEmpty(&quot;&quot;) == true

* StringUtils.isNullOrEmpty(&quot; &quot;) == false

* StringUtils.isNullOrEmpty(&quot;bob&quot;) == false

* StringUtils.isNullOrEmpty(&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.isEmpty();

}

Ob der String nur aus Weißraum besteht, testet die Methode nicht.

 

Zum Seitenanfang

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

Die vielleicht wichtigste Methode der Klasse String ist charAt(int index).[ 133 ](Der Parametertyp int verrät, dass Strings nicht größer als Integer.MAX_VALUE sein können, also nicht länger als 2.147.483.647 Zeichen. ) 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 bzw. gleich der Anzahl der Zeichen im String, so löst die Methode eine StringIndexOutOfBoundsException[ 134 ](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 (einer Sammlung von Bibliotheken für die Java EE-Entwicklung) gibt es aber JdbcUpdateAffectedIncorrectNumberOfRowsException – auch nicht von schlechten Eltern. ) mit der Fehlerstelle aus.

[zB]  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(int) stattfindet, ist zu überlegen, ob der String bei mehrmaligem Zugriff nicht stattdessen einmalig in ein eigenes Zeichen-Array kopiert werden sollte.

[»]  Hinweis *

Da Unicode-Zeichen auch aus 2 Java-chars zusammengesetzt sein können (Surrogate), gibt es eine alternative Methode int codePointAt(int index). Die Methode ist gegenüber charAt(…) langsamer, da eine lineare Suche erfolgen muss, denn es ist im Vorfeld nicht bekannt, an welcher Stelle ein Unicode-Zeichen aus genau einem char oder aus zwei chars steht. In diesem Buch gehen wir immer von Unicode-Zeichen aus, die in einem char kodiert werden können.

 

Zum Seitenanfang

5.5.5    Nach enthaltenen Zeichen und Zeichenfolgen suchen Zur vorigen ÜberschriftZur nächsten Überschrift

Die Objektmethode contains(CharSequence) testet, ob ein Teil-String (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 5.4    src/main/java/com/tutego/insel/string/EmailSpamChecker.java, Ausschnitt

public class EMailSpamChecker {



public static void main( String[] args ) {

String email1 = "Hallo Chris, alle fit im Schritt?";

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 bzw. Teil-Strings. Findet indexOf(…) nichts, liefert sie –1.

[zB]  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.

[zB]  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[ 135 ](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 folgt mehr in Abschnitt 1.7, »AdoptOpenJDK installieren«). ), um nach ganzen Zeichenfolgen zu suchen und die Startposition zurückzugeben.

[zB]  Beispiel

Aufruf von indexOf(String, int) mit der Suche nach einem Teil-String:

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 errechnet sich ausgehend von der alten Finder-Position. 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.

[zB]  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 Teil-Strings einer Zeichenkette *

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

Listing 5.5    src/main/java/com/tutego/insel/string/CountMatches.java, Ausschnitt

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

}

}

Die gewählte Implementierung liefert die Anzahl nicht überlappender Teil-Strings, würde also beim Aufruf frequency("aaaa", "aa") das Ergebnis 2 liefern, denn mit pos += part.length() geht die Implementierung immer so viele Schritte weiter nach rechts, wie der Teil-String lang ist. Je nach Interpretation der Aufgabe – und anderen Algorithmus – wäre auch 3 eine erlaubte Rückgabe, aber dann müsste die Schleife immer nur eine Position nach rechts gehen.

 

Zum Seitenanfang

5.5.6    Das Hangman-Spiel Zur vorigen ÜberschriftZur nächsten Überschrift

Die Methoden, die wir bisher schon kennengelernt haben, lösen geschätzte 90 % aller Aufgaben in der täglichen Programmierpraxis:

  • int charAt(int)

  • int length()

  • boolean equals(Object)

  • boolean contains(CharSequence)

  • int indexOf(char), int indexOf(String)

Genau diese 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 5.6    src/main/java/com/tutego/insel/string/Hangman1.java, Ausschnitt

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 = 0; ; guesses++ ) {

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 guessedChar = new java.util.Scanner( System.in ).next().charAt( 0 );



if ( usedChars.indexOf( guessedChar ) >= 0 )

System.out.printf( "%c hast du schon mal getippt!%n", guessedChar );

else { // Zeichen wurde noch nicht benutzt

usedChars += guessedChar;

if ( hangmanWord.indexOf( guessedChar ) >= 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", guessedChar );

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",

guessedChar );

}

}

}

}

Das Spiel startet mit einer Ausgabe wie:

Runde 0. Bisher geraten: ______________. Was wählst du für ein Zeichen?

Wir geben einen Buchstaben ein, etwa »e«, drücken dann (¢), und das Programm reagiert:

e

Gut geraten, 'e' gibt es im Wort. Aber es fehlt noch was!

Runde 1. Bisher geraten: ____________e_. Was wählst du für ein Zeichen?

a

Gut geraten, 'a' gibt es im Wort. Aber es fehlt noch was!

Runde 2. Bisher geraten: a____a___a__e_. Was wählst du für ein Zeichen?

Dann geht es weiter bis zum Ende des Spiels.

 

Zum Seitenanfang

5.5.7    Gut, dass wir verglichen haben Zur vorigen ÜberschriftZur nächsten Ü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.

  • Die switch-Anweisung ermöglicht den Vergleich von String-Objekten mit einer Liste von Sprungzielen. Intern führt equals(…) den Vergleich durch.

  • 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 Skriptsprachen 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 identisch sind, also zwei String-Objekte identisch sind. Die Gleichwertigkeit testet == bekanntermaßen nicht.

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.

[zB]  Beispiel

Bei dem Vergleich mit == ist das Ergebnis ein anderes als beim Vergleich 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 vorliegende "Passwort". Nur der equals(…)-Vergleich (2.1) und (2.2) ist hier korrekt, da hier die puren Zeichen verglichen werden, und die sind 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; der Test findet Zeichen für Zeichen statt.

[zB]  Beispiel
String str = "REISEPASS";

boolean result1 = str.equals( "Reisepass" ); // false

boolean result2 = str.equalsIgnoreCase( "ReISePaSs" ); // true
[»]  Methodenvergleich

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

Lexikografische Vergleiche mit Größer/Kleiner-Relation

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

[zB]  Beispiel

Wir vergleichen 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(String) größer 0.

Der von compareTo(String) 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 in »Java SE 9 Standard-Bibliothek« 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 10.4, »Vergleichen von Objekten und Ordnung herstellen«, 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 endet.

[zB]  Beispiel

Teste mit endsWith(String) eine Dateinamenendung und mit startsWith(String) eine Anrede:

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.

[zB]  Beispiel

Der Aufruf von regionMatches(…) ergibt true:

String  s = "Deutsche Kinder sind zu dick";

// Position: 0 9

boolean b = s.regionMatches( 9, "Bewegungsarmut bei Kindern", 19, 6 );

// Position: 0 19

Die Methode beginnt den Vergleich am neunten Zeichen, also bei »K« im String s, und beim 19. Buchstaben in dem Vergleichs-String, 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« true.

[zB]  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 );
 

Zum Seitenanfang

5.5.8    String-Teile extrahieren Zur vorigen ÜberschriftZur nächsten Überschrift

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

Teile eines Strings als String mit substring(…) erfragen

Wollen wir einen Teil-String 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.

[zB]  Beispiel

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

String s1 = "Welche Sprache spricht man in der Sauna? Schwyzerdütsch";

// Position: 0 41

String s2 = s1.substring( 41 ); // Schwyzerdütsch

Der Index von substring(int) gibt die Startposition (nullbasiert) 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 gilt somit, dass s.substring(0) für jeden beliebigen String s ungleich null gleich s ist.

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.

[zB]  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. Diese Angabe ist in Java üblich. 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.

[zB]  Beispiel

Konvertiere das erste Zeichen eines Textes in Großbuchstaben:

public static String firstToUpper( String string ){

if ( string.isEmpty() ) return "";

return string.substring( 0, 1 ).toUpperCase().concat( string.substring( 1 ) );

}

Als Notiz sei angemerkt, dass die erste Variante einer anderen Lösung gegenübersteht:

return Character.toUpperCase( string.charAt( 0 ) ) + string.substring( 1 );

Doch die erste Version hat den Vorteil, dass die Konvertierung auch berücksichtigt, wenn ein String etwa mit »ß« beginnt, denn das wird bisher zu »SS«. Zwar gibt es diesen Fall im Deutschen nicht, aber es gibt noch weitere Fälle in anderen Sprachen, bei denen aus einem Zeichen nach der Konvertierung in Großbuchstaben plötzlich zwei Zeichen werden; siehe dazu auch Abschnitt 5.5.9, »Strings anhängen, zusammenfügen, Groß-/Kleinschreibung und Weißraum«.

Selbstverständlich kommen nun diverse Indexüberprüfungen hinzu – eine StringIndexOutOfBoundsException[ 136 ](Die API-Dokumentation spricht von einer IndexOutOfBoundsException, doch StringIndexOutOfBoundsException ist eine Unterklasse davon. ) meldet fehlerhafte Positionsangaben wie bei charAt(int).

String vor/nach einem Trenn-String *

Die String-Klasse bietet keine einfache Bibliotheksmethode für den Fall an, dass ein Trennzeichen gegeben und ein Teil-String vor oder nach diesem Trennzeichen gefragt ist.[ 137 ](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. Mit regulären Ausdrücken oder mit split(…) von String lässt sich so etwas theoretisch lösen, aber elegant ist der Code auch nicht. ) 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", "." ) liefert "index".

  • substringAfter( "index.html", "." ) liefert "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 Teil-String vor bzw. hinter diesem gefundenen Trenn-String. 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 Leer-String. 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 5.7    src/main/java/com/tutego/insel/string/StringUtils.java, Ausschnitt

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 first 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 war es das dann schon?

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

Während charAt(int) nur ein Zeichen vom String liefert, kopiert getChars(int srcBegin, int srcEnd, char[] dest, int dstBegin) mehrere Zeichen aus einem angegebenen Bereich des Strings in ein übergebenes Array.

[zB]  Beispiel

Kopiere Teile des Strings in ein Array:

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 angegebenen 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 Array, 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 Speicherbereinigung. Daher ist diese Variante nicht empfehlenswert. chars() ist eine Methode ab Java 9, die einen Strom von Zeichen für die Zeichenkette liefert (siehe dazu auch Abschnitt 5.7, »CharSequence als Basistyp«).

Weitere Möglichkeiten zur Zerlegung

Es gibt zwei weitere Möglichkeiten, aus einem String gewisse Teile zu extrahieren. Zum einen erlaubt split(…) das Zerlegen einer Zeichenkette mithilfe eines regulären Ausdrucks. Dann gibt es noch seit Java 11 die Methode lines(), die einen Strom von Zeilen liefert, getrennt durch den Zeilenumbruch.

[zB]  Beispiel

Ein String besteht aus mehreren Zeilen. Schneide in jeder Zeile vorne und hinten den Weißraum ab, und gib alle Zeilen aus:

String text = " Masterpiece Tweeter\t  \n   Dolomiten";

text.lines().forEach( line -> System.out.println( line.trim() ) );

Das Beispiel greift auf Lambda-Ausdrücke zurück, die Thema von Kapitel 12, »Lambda-Ausdrücke und funktionale Programmierung«, sind.

 

Zum Seitenanfang

5.5.9    Strings anhängen, zusammenfügen, Groß-/Kleinschreibung und Weißraum Zur vorigen ÜberschriftZur nächsten Ü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

Um an einen String einen weiteren String anzuhängen, gibt es zwei naheliegende Möglichkeiten:

  • mit der String-Methode concat(String)

  • mit dem Plus-Operator. Das ist im Grunde flexibler, da Nicht-Strings vorher in Strings konvertiert werden.

Wir werden später sehen, dass es die StringBuilder/StringBuffer-Klassen noch weiter treiben und überladene append(…)-Methoden für unterschiedliche Datentypen anbieten.

[zB]  Beispiel

Hänge die aktuelle Zeit hinter eine Zeichenkette:

String s1 = "Die Uhrzeit ist: ";

String s2 = LocalTime.now().toString();

String s3 = s1.concat( s2 ); // Die Uhrzeit ist: 20:08:39.267490200

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

String s4 = "Die Uhrzeit ist: " + LocalTime.now().toString();

Es geht sogar noch kürzer, denn der Plus-Operator ruft automatisch toString() bei Objekten auf:

String s5 = "Die Uhrzeit ist: " + LocalTime.now();

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

Mehrere Strings zusammenfügen

Zwei überladene Klassenmethoden join(…) können mehrere Zeichenketten zusammenfügen und zwischen den Gliedern einen Trenn-String setzen. Die Methoden unterscheiden sich in der Parameterliste: Einmal kommen die Glieder direkt über ein Vararg und einmal über ein Iterable:

  • static String join(CharSequence delimiter, CharSequence... elements)

  • static String join(CharSequence delimiter, Iterable<? extends CharSequence>

    elements)

Die Argumente müssen Zeichenfolgen sein und können nicht etwa allgemeine Objekte sein, die automatisch in Strings konvertiert werden. Auch null dürfen die Glieder nicht sein.

[zB]  Beispiel

Füge die Wörter mit einem Leerzeichen zu einem Satz zusammen:

String s = String.join( " ", "Geht", "ein", "Mullah", "in", "die", "Bar." );

Der Trenn-String darf nicht null sein, kann aber durchaus leer sein.

In StringBuilder/StringBuffer gibt es keine vergleichbare Methode, wohl aber eine kleine Klasse StringJoiner, die auch die eigentliche Arbeit von join(…) vollzieht.

Groß-/Kleinschreibung

Die Klasse Character deklariert einige statische Methoden, die einzelne Zeichen in Groß-/Kleinbuchstaben umwandeln. 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 »SPASZ 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. Und seit dem 29. Juni 2017 ist das große Eszett Bestandteil der amtlichen deutschen Rechtschreibung; es liegt im Unicode-Alphabet an Position U+1E9E. Warten wir ab, ob der Konverteralgorithmus umgestellt wird.

[zB]  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 passt die neue Zeichenkette möglicherweise nicht mehr in den Speicherbereich. 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 Ergebnis-String hat das i keinen i-Punkt!

[»]  Hinweis

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

public String toLowerCase() {

return toLowerCase( Locale.getDefault() );

}

Die Voreinstellung muss nicht die beste sein.

Weißraum entfernen

In einer Benutzereingabe oder Konfigurationsdatei steht nicht selten vor oder hinter dem wichtigen Teil eines Textes Weißraum wie Leerzeichen oder Tabulatoren. Vor der Bearbeitung sollten sie entfernt werden. Die String-Klasse bietet dazu trim() und seit Java 11 strip(), stripLeading() und stripTrailing() an. Der Unterschied:

Methode

Entfernt …

trim()

… am Anfang und am Ende des Strings alle Codepoints kleiner oder gleich dem Leerzeichen 'U+0020'.

strip()

… alle Zeichen am Anfang und am Ende des Strings, die nach der Definition von Character.isWhitespace(int) Leerzeichen sind.

stripLeading()

wie strip(), allerdings nur am Anfang des Strings.

stripTrailing()

wie strip(), allerdings nur am Ende des Strings.

Tabelle 5.8    Unterschiede zwischen »trim()« und »stripXXX()«

Alle vier genannten Methoden entfernen keinen Weißraum inmitten des Strings.

[zB]  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.'
[zB]  Beispiel

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

boolean isBlank = "".equals( s.trim() );

Alternativ:

boolean isBlank = s.trim().isEmpty();
 

Zum Seitenanfang

5.5.10    Gesucht, gefunden, ersetzt Zur vorigen ÜberschriftZur nächsten Überschrift

Da String-Objekte unveränderlich sind, kann eine Veränderungsmethode nur einen neuen String mit den Veränderungen zurückgeben. Es gibt 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 alle Auftreten des Zeichens oldChar durch newChar.

  • 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, den ein regulärer Ausdruck beschreibt.

Ersetzen ohne reguläre Ausdrücke

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

[zB]  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 char-Array, führt die Ersetzungen durch und konvertiert das interne Zeichen-Array in ein String-Objekt, das 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 Ersetzungs-String kann auch leer sein, und so werden die Zeichen im Ergebnis gelöscht.

[zB]  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 und nehmen Ersetzungen vor. replaceFirst(…) ersetzt, wie der Name schon sagt, nur das erste Auftreten.

[zB]  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 Such-String 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 und auch schneller.

[zB]  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 folgender Zeichenkette:

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Der Punkt steht in regulären Ausdrücken für beliebige Zeichen. Erst wenn ein Backslash (\) den Punkt ausmaskiert (wegen des Sonderstatus des Backslashs als Escape-Sequenz in Strings 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. In »Java SE 9 Standard-Bibliothek« gehe ich noch intensiver auf reguläre Ausdrücke ein. Auch die API-Dokumentation http://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/regex/Pattern.html gibt neben einer vollständigen Dokumentation kleine Beispiele.

 

Zum Seitenanfang

5.5.11    String-Objekte mit Konstruktoren und aus Wiederholungen erzeugen * Zur vorigen ÜberschriftZur nächsten Überschrift

Die String-Klasse hat diverse Methoden, die neue String-Objekte aufbauen, insbesondere weil String-Objekte immutable sind. Zu den Methoden zählen substring(…), join(…), format(…).

Neue String-Objekte mit Konstruktoren aufbauen

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. Der Konstruktor ist im Prinzip unnötig, da String-Objekte unveränderbar (immutable) sind.

  • String(char[] value)

    Erzeugt ein neues Objekt und kopiert die im value vorhandenen Zeichen in das neue String-Objekt.

  • String(char[] value, int offset, int length)

    Erzeugt wie String(char[]) einen String aus einem Ausschnitt eines char-Arrays. Der verwendete Ausschnitt beginnt bei dem Index offset und umfasst length Zeichen.

  • String(byte[] bytes)

    Erzeugt ein neues Objekt aus dem Array bytes. 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-Arrays.

  • 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.

[zB]  Beispiel

Erzeuge einen String einer gegebenen Länge:

public static String generateStringWithLength( int len, char fill ) {

char[] chars = new char[ len ];

Arrays.fill( chars, fill );

return new String( chars );

}

In der String-Klasse gibt es keine Methode, die eine Zeichenkette einer vorgegebenen Länge aus einem einzelnen Zeichen erzeugt.

[zB]  Beispiel

Teste, ob zwei Zeichenketten – unabhängig von der Groß-/Kleinschreibung – 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(…) sortiert ein Array, in dem Fall das char-Array. Ist die Groß-/Kleinschreibung relevant, kann gleich Arrays.equals(char[], char[]) herangezogen werden.

Ü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. Der Konstruktor ist im Grunde unnötig.

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 die JVM sie im Programmverlauf benutzt und welche Klassen den String referenzieren. Dieses String-Objekt »lebt« in einem Bereich, der Konstantenpool genannt wird.[ 138 ](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.

[zB]  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[ 139 ](Das Zitat stammt übrigens von Bill Joy (eigentlich heißt er William Nelson Joy), der Sun Microsystems mitgründete. 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 und dem vi-Editor. ) automatisch zu einer großen Zeichenkette zusammen, sodass keine Konkatenation zur Laufzeit nötig ist. Konstante Ausdrücke »rechnet« der Compiler immer direkt selbst aus.

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.

Einen 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 einen Null-String unproblematisch sind, führen Dereferenzierungen auf der null-Referenz unweigerlich zu einer NullPointerException:

String s = null;

System.out.println( s ); // Ausgabe: null

s.length(); // inline image NullPointerException

printXXX(null) führt zu der Konsolenausgabe »null« und zu keiner Ausnahme, da es eine Fallunterscheidung in printXXX(Object) und printXXX(String) gibt, die die null-Referenz als Sonderfall betrachtet.[ 140 ](In der Implementierung von PrintStream: 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.

Strings aus Wiederholungen generieren

In Java 11 ist eine Objektmethode repeat(int count) eingezogen, die einen gegebenen String vervielfacht.

[zB]  Beispiel

Wiederhole den String s dreimal:

String s = "tu";

System.out.println( s.repeat( 3 ) ); // tututu

Bevor es die Methode in Java 11 gab, sah eine alternative Lösung etwa so aus:

int    n = 3;

String t = new String( new char[ n ] ).replace( "\0", s );

System.out.println( t ); // tututu

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern