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

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 4 Der Umgang mit Zeichenketten
  gp 4.1 Strings und deren Anwendung
    gp 4.1.1 String-Literale als String-Objekte für konstante Zeichenketten
    gp 4.1.2 String-Objekte neu anlegen
    gp 4.1.3 String-Länge
    gp 4.1.4 Gut, dass wir verglichen haben
    gp 4.1.5 String-Teile extrahieren
    gp 4.1.6 Suchen und Ersetzen
    gp 4.1.7 Veränderte Strings liefern
    gp 4.1.8 Unterschiedliche Typen in Zeichenketten konvertieren
  gp 4.2 Veränderbare Zeichenketten mit StringBuffer/StringBuilder
    gp 4.2.1 Anlegen von StringBuffer/StringBuilder-Objekten
    gp 4.2.2 Die Länge eines StringBuffer/Builder-Objekts lesen und setzen
    gp 4.2.3 Daten anhängen
    gp 4.2.4 Zeichen(folgen) setzen, erfragen, löschen und umdrehen
  gp 4.3 Vergleiche von Zeichenketten
    gp 4.3.1 equals() in String und StringBuffer/StringBuilder
    gp 4.3.2 equals() und hashCode() bei StringBuffer/StringBuilder
    gp 4.3.3 Sprachabhängiges Vergleichen mit der Collator-Klasse
    gp 4.3.4 Effiziente interne Speicherung für die Sortierung
  gp 4.4 Reguläre Ausdrücke
    gp 4.4.1 Die Klassen Pattern und Matcher
    gp 4.4.2 Mit MatchResult alle Ergebnisse einsammeln
    gp 4.4.3 Das alternative Paket org.apache.regexp
  gp 4.5 Zerlegen von Zeichenketten
    gp 4.5.1 Splitten von Zeichenketten mit split() aus Pattern
    gp 4.5.2 split() in String
    gp 4.5.3 Die Klasse Scanner
    gp 4.5.4 StringTokenizer
    gp 4.5.5 Der BreakIterator als Wort- und Satztrenner
  gp 4.6 Zeichenkodierungen
    gp 4.6.1 Kodierungen für unterschiedliche Codepages
    gp 4.6.2 Andere Klassen zur Konvertierung und das Paket java.nio.charset
    gp 4.6.3 Base64-Kodierung
  gp 4.7 Formatieren von Ausgaben
    gp 4.7.1 Zahlen, Prozente und Währungen mit NumberFormat formatieren
    gp 4.7.2 Dezimalzahlformatierung mit DecimalFormat
    gp 4.7.3 Formatieren mit format() aus String
    gp 4.7.4 Ausgaben formatieren mit MessageFormat

Kapitel 4 Der Umgang mit Zeichenketten

Ohne Unterschied macht Gleichheit keinen Spaß. – Dieter Hildebrandt


Galileo Computing

4.1 Strings und deren Anwendung  downtop

Ein String ist eine Sammlung von Zeichen, die im Speicher geordnet abgelegt werden. Die Zeichen sind einem Zeichensatz entnommen, der in Java dem 16 Bit Unicode-Standard entspricht – mit etwas Umwegen ist auch Unicode 4 mit 32 Bit Zeichen möglich. Wenn ein einzelnes Zeichen in einer Variablen vom Typ char gespeichert werden kann, können Zeichenfolgenden in einem char-Feld gehalten werden. Die Feld-Variante ist jedoch etwas mühselig, denn wir müssten uns über viele Dinge selbst kümmern, etwas um die Verschiebung der Zeichen beim Löschen und Einfügen in der Mitte, um die Verwaltung des Puffers, und vieles mehr.

Java sieht drei Klassen vor, die Zeichenfolgen von der Implementierung abstrahieren. Die Klassen entsprechen der idealen Umsetzung der objektorientierten Idee (mit der wir uns in Kapitel 6 intensiv auseinander setzten werden): Die Daten werden gekapselt, was heißt, die tatsächliche Zeichenkette ist in der Klasse als privates Feld gegen Zugriffe von außen gesichert, und selbst die Länge ist ein privates Attribut der Klasse, die nur über eine Methode zugänglich ist.

gp  Die Klasse String repräsentiert Zeichenketten, die sich nicht ändern. Objekte, deren Zustand nicht mehr verändert werden können, heißen immutable; und der Typ String ist ein Beispiel einer solchen immutable Klasse. Mit Objekten vom Typ String lässt sich nach Zeichen oder Teilzeichenketten suchen, und ein String lässt sich mit einem anderen String vergleichen, aber es können keine Zeichen im String verändert werden. Es gibt einige Methoden, die scheinbar Veränderungen an Strings vornehmen, aber sie erzeugen in Wahrheit neue String-Objekte, die die veränderten Zeichenreihen repräsentieren. So entsteht beim Aneinanderhängen zweier String-Objekte als Ergebnis ein drittes String-Objekt für die zusammengefügte Zeichenreihe.
gp  Die Klassen StringBuffer und StringBuilder repräsentieren im Gegensatz dazu dynamische, beliebig änderbare Zeichenreihen. Der Unterschied zwischen StringBuffer und StringBuilder ist lediglich, dass StringBuffer gegen nebenläufige Operationen sicher ist, während das auf StringBuilder nicht zutrifft.

In Java ist eine Symbiose zwischen String als Objekt und String als eingebautem Datentyp vorgenommen worden. Die Sprache ermöglicht zwar die direkte Konstruktion von String-Objekten etwa aus String-Literalen (Zeichenketten in doppelten Anführungszeichen) und die Konkatenation (Aneinanderreihung von Strings mit +) von mehreren Strings, doch intern ist die Aneinanderreihung über Methoden der Klassen String, StringBuffer beziehungsweise StringBuilder realisiert. Mit dem Plus auf String-Objekten ist also ein besonderer Operator auf der Klasse String definiert, so wie er 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

4.1.1 String-Literale als String-Objekte für konstante Zeichenketten  downtop

Damit wir Zeichenketten nutzen können, muss ein Objekt der Klasse String oder StringBuffer/StringBuilder erzeugt worden sein. Nutzen wir String-Literale, so müssen wir die String-Objekte nicht immer von Hand mit new erzeugen, denn alles, was in doppelten Anführungszeichen steht, ist automatisch ein String-Objekt und das wird von der Java-Virtuellen Maschine schon bereitgestellt. Das bedeutet auch, dass hinter dem String-Literal gleich ein Punkt für den Methodenaufruf stehen kann.


Beispiel   Teste unabhängig von der Groß-/Kleinschreibung, ob der String s mit »Lachgummi« gleich ist.
boolean isLachgummi =   "Lachgummi".  equalsIgnoreCase( s );

Die JVM erzeugt für jedes Zeichenketten-Literal automatisch ein entsprechendes String-Objekt, was für jede konstante Zeichenkette höchstens einmal geschieht, egal, wie oft sie im Programmverlauf benutzt wird. Dieses String-Objekt lebt in einem Bereich, der Konstantenpool genannt wird. Im Folgenden gibt es also die Zeichenfolge »Habseligkeiten« als String-Objekt nur ein einziges Mal.

out.println( "Habseligkeiten" );
out.println( "Habseligkeiten".length() );

Galileo Computing

4.1.2 String-Objekte neu anlegen  downtop

Liegt die Zeichenkette nicht als String-Literal vor, lassen sich mit den unterschiedlichen Konstruktoren der String-Klasse neue Objekte aufbauen.


final class java.lang  .String
  implements CharSequence, Comparable<String>, Serializable

gp  String() Erzeugt ein neues Objekt ohne Zeichen (den leeren String »«).
gp  String( String string ) Erzeugt ein neues Objekt mit einer Kopie von string. Es wird selten benötigt, da String-Objekte unveränderbar (immutable) sind.
gp  String( char[] value ) Erzeugt ein neues Objekt und konvertiert die im char-Feld vorhandenen Zeichen in das String-Objekt.
gp  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.
gp  String( byte[] bytes ) Erzeugt ein neues Objekt aus dem Bytefeld. Das Byte-Array enthält keine Unicode-Zeichen, sondern eine Folge von Bytes, die nach der Standardkodierung der jeweiligen Plattform in Zeichen umgewandelt werden.
gp  String( byte[] bytes, int offset, int length ) Erzeugt wie String(byte[]) einen String aus einem Ausschnitt eines Bytefelds.
gp  String( byte[] bytes, String ) throws UnsupportedEncodingException Erzeugt einen neuen String von einem Byte-Array mithilfe einer speziellen Zeichenkodierung, die die Umwandlung von Bytes in Unicode-Zeichen festlegt.
gp  String( byte[] bytes, int offset, int length, String ) throws UnsupportedEncodingException Erzeugt einen neuen String mit einem Teil des Byte-Arrays mithilfe einer speziellen Zeichenkodierung.
gp  String( StringBuffer buffer ) Erzeugt aus einem veränderlichen StringBuffer-Objekt ein unveränderliches String-Objekt, das dieselbe Zeichenreihe repräsentiert.
gp  String( StringBuilder builder ) Erzeugt den String aus einem StringBuilder.
gp  String( int[] codePoints, int offset, int count ) Neu in Java 5 sind die Codepoints, die Zeichen über int kodieren.

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


Beispiel   Erzeuge aus einem char-Feld mit Vokalen ein String-Objekt.
char[] vocalsArray = { 'a', 'e', 'i', 'o', 'u' };
String vocal =   new String( vocalsArray )  ;

Der Konstruktor String(String)

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

String str = "Wer ist Rudolf Wijbrand Kesselaar?";
String str = new String( "Wer ist Rudolf Wijbrand Kesselaar?" );

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 nur für eine Optimierung zu gebrauchen. Immer wenn mit substring() ein Teilstring aufgebaut wurde, wird die ganze Zeichenfolge als char-Feld intern referenziert. Um den Speicherbedarf in diesem Fall zu optimieren, ist dieser Konstruktor gut geeignet.

Leerer String, Leer-String oder Null-String

Durch

String str = "";

oder

String str = new String();

werden String-Objekte erzeugt, die keine Zeichen enthalten. Diesen String nennen wir dann leeren String, Leer-String oder Null-String. Der letzte Begriff ist leider etwas unglücklich und führt oft zur Verwechslung mit Folgendem:

String s = null;
System.out.println( s );              // null

Hier bekommen wir keinen leeren String und bei der Benutzung im Methodenaufruf, etwa s.length(), eine NullPointerException.

Strings einer gegebenen Länge erzeugen

An einigen Stellen in den Bibliotheken gibt es noch Nachholbedarf – das gilt insbesondere für die Klasse String. Zum Beispiel gibt es keine Funktion, die eine Zeichenkette einer vorgegebenen Länge aus einem einzelnen Zeichen erzeugt. Selbst einfachste BASIC-Dialekte verfügen über solche Funktionen. In Java müssen wir sie selbst entwickeln.

Zunächst ist zu fragen, ob die Zeichenkette als String oder als StringBuffer/StringBuilder bereitgestellt werden soll. Möchten wir das Ergebnis als String, können wir erst ein char-Feld aufbauen, das dann mit Arrays.fill() und einem gewünschten Zeichen gefüllt wird. Im zweiten Schritt konvertieren wir das Feld in ein String-Objekt:

Listing 4.1   Aus GenerateStringWithGivenLength.java

public static String generateStringWithLength( int lenchar fill )
{
  if ( len < 0 )
    return null;
  char[] cs = new char[ len ];
  Arrays.fill( csfill );
  return new String( cs );
}

Galileo Computing

4.1.3 String-Länge  downtop

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.


Beispiel   "Hallo".length() hat fünf Zeichen. Leerzeichen und Sonderzeichen werden mitgezählt.

Um herauszufinden, ob der String keine Zeichen hat, lässt sich neben length() == 0 auch unter Java 5 die Methode isEmpty() nutzen.


Galileo Computing

4.1.4 Gut, dass wir verglichen haben  downtop

Um Strings zu vergleichen, gibt es viele Möglichkeiten und Optionen. Oft wollen wir einen konstanten String mit einer Benutzereingabe vergleichen. Hier gibt es die aus der Klasse Object geerbte, doch in der Klasse String überschriebene Methode equals(). Die Methode gibt true zurück, falls die Strings Zeichen für Zeichen übereinstimmen. Groß- und Kleinschreibung werden dabei unterschieden. Mit equalsIgnoreCase() werden zwei Zeichenketten verglichen, ohne dass auf die Groß-/Kleinschreibung geachtet wird.


Beispiel   equals() liefert für result1 den Wert false und equalsIgnoreCase() für result2 den Wert true.
String str = "REISEPASS";
boolean result1 = str.  equals  ( "Reisepass" );             // false
boolean result2 = str.  equalsIgnoreCase  ( "ReISePaSs" );   // true

Sortierung mit der Größer/Kleiner-Relation

Wie equals() und equalsIgnoreCase() vergleichen auch die Methoden compareTo(String) und compareToIgnoreCase(String) zwei Strings. equals() aus String überschreibt die Methode equals() aus Object, sodass der Parametertyp auch Object ist. Der Argumenttyp beim Aufruf kann natürlich völlig anders sein, doch Gleichheit stellt equals()nur dann fest, wenn das Argument auch vom Typ String ist. (Bei beliebigen Objekten wird nicht automatisch die Methode toString() aufgerufen.) Selbst Vergleiche mit einem inhaltsgleichen StringBuffer-Objekt ergeben immer false – ein StringBuffer ist kein String. Der Rückgabewert von compareTo() ist auch 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 Sortierfunktion wichtig. Der Sortieralgorithmus muss beim Vergleich zweier Strings wissen, wie sie einzusortieren sind.


Beispiel   Ist s der String "Justus", dann gilt:
s.  compareTo  ( "Bob" )     > 0   //"Justus" ist lexikographisch größer als "Bob"
s.  compareTo  ( "Justus" ) == 0
s.  compareTo  ( "Peter" )   < 0

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

compareToIgnoreCase() ist vergleichbar mit equalsIgnoreCase(), bei der die Groß-/Kleinschreibung keine Rolle spielt. Bei Sun wird dies intern mit einem Comparator implementiert, der zwei beliebige Objekte – für Zeichenketten natürlich vom Typ String – in eine Reihenfolge bringt.

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. "http://java-tutor.com".startsWith("http") ergibt true. Eine ähnliche Funktion gibt es für Suffixe: endsWith(). Sie überprüft, ob ein String mit einer Zeichenfolge am Ende übereinstimmt.


Beispiel   Die Methode endsWith() ist praktisch zum Testen von Dateinamenendungen:
String filename  = "Echolallie.gif";
boolean issesGif = filename.  endsWith  ( ".gif" );    // true

String-Teile vergleichen

Eine Erweiterung der Ganz-oder-gar-nicht-Vergleichsfunktionen bietet die Methode regionMatches(), mit der Teile einer Zeichenkette mit Teilen einer anderen verglichen werden können. 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 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";
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 Funktion true.
s.regionMatches( true, 9, "Bewegungsarmut bei Kindern", 19, 6 );


Galileo Computing

4.1.5 String-Teile extrahieren  downtop

Die vielleicht wichtigste Funktion 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 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 0 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.

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 = "Body-Mass-Index  = Körpergewicht (kg) / Körpergröße (m) / Körpergröße (m)";
char[] chars = new char[13];
s.  getChars  ( 18, 18 + 13, chars, 0 );
s.getChars() kopiert ab Position 18 aus dem String s 13 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 im obigen Beispiel. 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, so können wir die Methode toCharArray() verwenden. Für häufigen Zugriff auf einen String bewirkt dies eine Geschwindigkeitssteigerung. toCharArray() arbeitet intern auch mit getChars(). Als Ziel-Array legt die Methode ein neues Array an, welches wir dann zurückbekommen.


Beispiel   Die untersten vier Bits von i sind in eine hexadezimale Ziffer umzuwandeln:
char c = "0123456789ABCDEF".toCharArray()[i & 15];
Für diesen speziellen Fall wäre charAt() zwar schneller gewesen, doch demonstriert das Beispiel, dass wir per [] auch direkt auf die Array-Elemente eines Methodenergebnisses zugreifen können. Das ist völlig korrekt, denn toCharArray() liefert ein Array als Ergebnis.

Teile eines Strings als String

Wollen wir bei den Teilstrings keine Zeichenfelder bekommen, sondern bei dem Typ String bleiben, so greifen wir zur Methode substring(), die in zwei Varianten existiert. Sie liefern beide ein neues String-Objekt zurück, das einem Teil des Originals entspricht.


Beispiel   substring(int) liefert eine Teilzeichenkette ab einem Index bis zum Ende. Das Ergebnis ist ein neues String-Objekt.
String s1 = "Die erste McDonalds Filiale öffnete 1971 in München";
String s2 = s1.  substring  ( 44 );                  // München
String s2 ist dann mit dem Endstück »München« belegt.

Der Index von substring() gibt die Startposition an, ab der Zeichen in die neue Teilzeichenkette kopiert werden. substring() liefert den Teil von diesem Zeichen bis zum Ende des ursprünglichen Strings.

Wollten wir die Teilzeichenkette genauer spezifizieren, so nutzen wir die zweite Variante von substring(). Ihre Parameter erwarten den Anfang und das Ende des gewünschten Ausschnitts:

String s1 = "fettleibig : adipös";
String s2 = s1.  substring  ( 48 );       // leib

Wie man sieht, bezeichnet die Endposition das erste Zeichen des ursprünglichen Strings, das nicht mehr zur Teilzeichenkette gehören soll. Bei genauerer Betrachtung ist substring(int) nichts anderes als eine Spezialisierung von substring(int, int), denn die erste Variante mit dem Startindex lässt sich auch schreiben als:

s.substring( beginIndexs.length() );

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

Strings rechtsbündig setzen

Falls wir immer ein fixes Zeichen verwenden und die String-Länge in einem festen Bereich bleibt, so ist eine andere Möglichkeit noch viel eleganter (aber nicht unbedingt schneller). Sie arbeitet mit der substring()-Methode. Wir schneiden aus einem großen String mit festen Zeichen einfach einen String mit der benötigten Länge heraus. Damit lässt sich auch flott eine Zeile formulieren, die einen Text mit so vielen Leerzeichen füllt, dass dieser rechtsbündig ist.

Listing 4.2   StringAlignRight.java

public class StringAlignRight
{
  public static String alignRight( String s )
  {
    return "                  ".substring( s.length() ) + s;
  }
  public static void main( String[] args )
  {
    System.out.println( alignRight("Huhu") );    // '              Huhu'
  }
}

Da seit Java 5 die Methode format() mit im Boot ist, sieht die Lösung viel einfacher aus:

System.out.format( "|%10.2f|%n"123.456 ); // |    123,46|
System.out.format( "|%-10.2f|"123.456 );  // |123,46    |

Galileo Computing

4.1.6 Suchen und Ersetzen  downtop

Um die erste Position eines Zeichens im String zu finden, verwenden wir die indexOf()-Methode. Als Argument lässt sich unter anderem ein Zeichen oder ein String vorgeben, der gesucht wird.


Beispiel   Ein Zeichen mit indexOf() suchen
String str = "Ernest Gräfenberg";
int index = str.indexOf( 'e' );                     // 3

Im Beispiel ist index gleich 3, da an der Position 3 das erste Mal ein »e« vorkommt. Die Zeichen in einem String werden wie Array-Elemente ab 0 durchnummeriert. Falls das gesuchte Zeichen im String nicht vorkommt, gibt die Methode indexOf() als Ergebnis –1 zurück.


Beispiel   Um das nächste »e« zu finden, können wir die zweite Version von indexOf() verwenden.
index = str.  indexOf  ( 'e', index + 1 );              // 11

Mit dem Ausdruck index+1 als Argument der Methode wird in unserem Beispiel ab der Stelle 4 weitergesucht. Das Resultat der Methode ist dann 11. Ist der Index kleiner 0, so wird dies ignoriert und 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 )
{
  ...
}

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


Beispiel   Hierzu dient die Methode lastIndexOf().
String str = "Gary Schubach";
int index  = str.  lastIndexOf  ( 'a' );                 // 10

Hier ist index gleich 10. Genauso wie bei indexOf() existiert eine überladene Version, die rückwärts ab einer bestimmten Stelle nach dem nächsten Vorkommen von »a« sucht. Wir schreiben:

index = str.lastIndexOf( 'a'index – 1 );

Nun ist der Index 1.


Hinweis   Die Parameter der char-orientierten Methoden indexOf() und lastIndexOf() sind alle vom Typ int und nicht, wie erwartet, 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, so dass 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.

Es gibt noch eine weitere Version von indexOf() und lastIndexOf(), die nach einem Teil-string (engl. substring) sucht. Die Versionen erlauben ebenfalls einen zweiten Parameter, der den Startindex bestimmt.


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.

Das Idiom s.indexOf(t) > –1 ist seit Java 5 nicht mehr nötig, um zu testen, ob ein Teil-string t im String s vorkommt, da es seit dem die Methode contains() gibt.

Zeichen ersetzen

Da String-Objekte unveränderlich sind, kann eine Veränderungsfunktion nur einen neuen String mit den Veränderungen zurückgeben. Die replace()-Methode ist ein Beispiel für diese Vorgehensweise.


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, nur ein neues String-Objekt mit dem Inhalt »Hunululu« wird erzeugt und von replace() zurückgegeben.

Die replace()-Methode betrachtet dafür intern eine Kopie des Zeichenfelds und geht dann Schritt für Schritt das Feld ab und führt die Ersetzungen aus. Die replace()-Methode ersetzt dabei alle Zeichen. Eine Variante, die nur das erste Zeichen ersetzt, müssen wir uns selbst schreiben.

Suchen und ersetzen

In Java 5 wurde die Methode replace() eingeführt, die in einem String alle auftretenden Strings sucht und durch einen anderen String ersetzt.

String s = "'Tag, Karl.''Wie geht's,Karl?''Gut, Karl.''Kahl, Karl?''Ja, Karl, 
  ganz kahl.'";
System.out.println( s.replace(".""!") );

Hier ersetzen wir alle Punkte durch Ausrufezeichen, sodass das Ergebnis ist:

'Tag, Karl!' 'Wie geht'sKarl?' 'GutKarl!' 'KahlKarl?' 'JaKarlganz kahl!'

Suchen und ersetzen mit regulären Ausdrücken

Die Funktionen replaceAll() und replaceFirst() suchen in Zeichenketten mit Hilfe von regulären Ausdrücken und nehmen Ersetzungen vor; replaceFirst() ersetzt, wie der Name schon sagt, nur das erste Auftreten.


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.replaceAll("Schnecke", "Katze") );
Das Ergebnis auf dem Bildschirm ist »Katzen erschrecken, wenn Katzen an Katzen schlecken, weil zum Schrecken vieler Katzen, Katzen nicht schmecken.«

Für ganz allgemeine Ersetzungsaufgaben eignen sich replaceAll() und replaceFirst() nicht; hier ist die replace()-Funktion passend. Da der Suchstring immer ein regulärer Ausdruck ist, können Sonderzeichen auftauchen, die eine besondere Rolle spielen. So müssen zum Beispiel Dollar oder Punkt mit \ ausmaskiert werden. Sollen in unserer Karl-Zeichenfolge wieder alle Punkte durch Ausrufezeichen ersetzt werden, so würde s.replaceAll(".", "!") nicht zum Erfolg führen, sondern nur zu einer Zeichenkette

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

Der Punkt steht in regulären Ausdrücken für beliebige Zeichen. Erst wenn dieser mit \\ ausmaskiert wird, liefert es wie in s.replaceAll("\\.", "!") das gewünschte Ergebnis.


Galileo Computing

4.1.7 Veränderte Strings liefern  downtop

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 vorgenommen, 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 öfters mit dem Plus-Operator realisiert. Die Methode von String dazu heißt concat(String). Wir werden später sehen, dass die StringBuffer-Klasse dies noch weiter treibt und eine Methode append() mit der gleichen Funktionalität anbietet, die Methode aber für unterschiedliche Typen überladen ist. Das steckt auch hinter dem Plus-Operator. Der Compiler wandelt dies automatisch in eine Kette von append()-Aufrufen um.


Beispiel   Hänge hinter eine Zeichenkette das aktuelle Tagesdatum:
String s1 = "Das aktuelle Datum ist: ";
String s2 = new Date().toString();
String s3 = s1.  concat  ( s2 ); // Das aktuelle Datum ist: Tue Jun 05 14:46:41 CEST 2005

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. (Aber wie das so ist: Sieht nett aus, aber ...)


Beispiel   Ähnlich wie im oberen Beispiel können wir 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 definiert einige statische Methoden, um einzelne Zeichen in Groß-/Kleinbuchstaben 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 kein 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 Klein und umgekehrt:
String s1 = "Spaß in der Naßzelle.";
String s2 = s1.  toLowerCase().toUpperCase()  ;      // 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. Wir gehen in einem eigenen Kapitel auf Sprachumgebungen und die Klasse Locale ein. Die parameterlosen Methoden wählen die Sprachumgebung gemäß den Länder-Einstellungen des Betriebssystems:

public String toLowerCase() {
  return toLowerCase( Locale.getDefault() );
}

Ähnliches steht bei toUpperCase().

Leerzeichen entfernen

In einer Benutzereingabe oder Konfigurationsdatei stehen nicht selten vor oder hinter dem wichtigen Teil eines Texts Leerzeichen. Vor der Bearbeitung sollten sie entfernt werden. Die String-Klasse bietet dazu trim() an. Diese Methode entfernt Leer- und ähnliche Füllzeichen am Anfang und Ende eines Strings. Andere Trendy-Sprachen wie Visual Basic bieten dazu noch trim()-Funktionen an, die nur die Leerzeichen vorher oder nachher verwerfen. Die Java-Bibliothek bietet das leider nicht.


Beispiel   Leerzeichen zur Konvertierung einer Zahl abschneiden:
String s = " 1234    ".  trim()  ;            // s = "1234"
int i = Integer.parseInt( s );            // i = 1234

Die Konvertierungsfunktion selbst schneidet keine Leerzeichen ab und würde einen Parserfehler melden. Die Helden der Java-Bibliothek haben allerdings bei Float.parseFloat() und Double.parseDouble() anders gedacht. Hier wird die Zeichenkette vorher schlank getrimmt. parseInt() unterstützt verschiedene Zahlensysteme, nicht jedoch Gleitkommazahlen. Auch die Verwandtschaft zwischen den Methoden valueOf() und parseXXX() ist in der Klasse Integer gerade andersherum beschrieben als bei Double und Float.


Galileo Computing

4.1.8 Unterschiedliche Typen in Zeichenketten konvertieren  toptop

Bevor ein Datentyp auf dem Bildschirm ausgegeben werden kann, zum Drucker geschickt oder in einer ASCII-Datei gespeichert wird, muss er in einen String konvertiert werden. Wenn wir etwa die Zahl 7 ohne Umwandlung ausgeben würden, hätten wir keine 7 auf dem Bildschirm, sondern einen Pieps.

Die String-Repräsentation eines primitiven Werts oder eines Objekts kennt die überladene Methode valueOf(). Sie konvertiert einen Datentyp in einen String. Alle valueOf()-Methoden sind statisch.


Beispiel   Konvertierungen einiger Datentypen in Strings:
String s1 = String.  valueOf  ( 10 );             // "10"
String s2 = String.  valueOf  ( Math.PI );        // "3.141592653589793"
String s3 = String.  valueOf  ( 1 < 2 );          // "true"
String s4 = String.  valueOf  ( new Date() );     // "Tue Jun 03 14:40:38 CEST 2003"

Sehen wir uns abschließend die Implementierungen von valueOf(Object) an:

public static String valueOf( Object obj ) {
  return (obj == null) ? "null" : obj.toString();
}

Die String-Umsetzung wird an das Objekt delegiert, denn jedes besitzt eine toString()-Methode. Die Sonderbehandlung testet, ob null übergeben wurde und liefert dann den gültigen String »null«.




1  Mit 31 Zeichen gehört dieser Methodenname schon zu den längsten. Übertroffen wird er aber noch um 5 Zeichen von TransformerFactoryConfigurationError.

2  Wieder ein Hinweis, dass Visual Basic einfach die bessere Sprache ist ...

 << zurück




Copyright © Galileo Press GmbH 2005
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 GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de