Kurzeinstieg Java: Kleine Textbearbeitung

Aus Wikibooks

Was sind Strings?[Bearbeiten]

Strings (dt. etwa "Zeichenreihe" oder "Zeichenkette"), sind Zeichenketten variablen Länge. Jeder beliebige Text, einschließlich Leerzeichen kann als String verwendet werden. Der Datentyp String ist eine Klasse mit einer Vielzahl von Methoden, von denen einige in diesem Kapitel vorgestellt werden.

Wie erzeugt man Strings?[Bearbeiten]

In den folgenden beiden Beispielen beschwert sich Scotty über ein Problem mit dem Warpkern:

public class Scotty {

  public static void main(String[] args) {
   String s = new String("Captain, wir haben ein Problem mit dem Warpkern.");
      System.out.println(s + " Stringlänge: " + s.length());
  } 
}
public class Scotty {

  public static void main(String[] args) {
      String s = "Captain, wir haben ein Problem mit dem Warpkern.";
      System.out.println(s + " Stringlänge: " + s.length());
  }
}

Beide Programme verhalten sich absolut identisch. Im ersten Beispiel wird ein String-Objekt erzeugt, in dem auf klassische Weise new aufgerufen wird. new in Verbindung mit dem Klassennamen ruft den Konstruktor der Klasse auf, der das Exemplar erstellt. Im zweiten Beispiel zeigen wir, dass ein String-Literal dasselbe ist wie ein per new erzeugter String. Welcher dieser beiden Varianten Sie in ihren Programmen den Vorzug geben ist ganz Ihnen überlassen. Es gibt viele weitere Konstruktoren, es ist String s = new String() ebenfalls eine Möglichkeit, einen neuen String zu erzeugen. Das wiederum ist dasselbe wie String s = "".

Strings kann man auch aus einem Zeichenfeld char[] erstellen:

 public static void main(String[] args) {
      char[] scottysArray = {'C', 'a', 'p', 't', 'a', 'i', 'n',
        ',', ' ', 'w', 'i', 'r', ' ', 'h', 'a', 'b', 'e', 'n',
        ' ', 'e', 'i', 'n', ' ',
        'P', 'r', 'o', 'b', 'l', 'e', 'm', ' ',
        'm', 'i', 't', ' ', 'd', 'e', 'm', ' ',
        'W', 'a', 'r', 'p', 'k', 'e', 'r', 'n', '.'};
      String s = new String(scottysArray);

Hier wird ein Konstruktor aufgerufen, der das Exemplar mit einem Feld initialisiert.

Wie vergleicht man Strings miteinander?[Bearbeiten]

Zunächst die schlechte Nachricht: Der Operator == hat in Bezug auf den Inhalt von Strings keine Bedeutung. Für typische Vergleiche von Strings miteinander muss man also ein bisschen mehr schreiben:

public class Spock {

    public static void main(String[] args) {
        
        String spocksWort = "Unlogisch!";
        
        // Exakte Schreibung
        if(spocksWort.equals("Unlogisch!")) {
            System.out.println("So rot kann der Alarm gar nicht sein.");
        }
        else if(spocksWort.equalsIgnoreCase("UNLOGISCH!")) {
            System.out.println("Faszinierend!");
        }
    }
}

Um die exakte Gleichheit von zwei Strings zu überprüfen nimmt man equals(). Kommt es auf die Groß- und Kleinschreibung nicht so an, dann equalsIgnoreCase().

Möchte man herausfinden, ob ein String mit einem bestimmten String beginnt oder endet, dann kommen die Methoden startsWith() und endsWith() zum Einsatz. Ob ein String einen anderen enthält, überprüft man mit contains():

public class McCoy {

    public static void main(String[] args) {
        
        String s1 = "McCoy: Die einen Menschen altern schneller und die anderen langsamer.";
        if(s1.startsWith("McCoy:")) {
            System.out.println("Achtung, McCoy spricht.");
        }
        if(s1.endsWith(".")) {
            System.out.println("* ein Satz");
        }
        if(! s1.contains("Spock")) {
            System.out.println("* es geht vermutlich nicht um Spock");
        }
    }
}

Wie liest man Strings von der Tastatur ein?[Bearbeiten]

Bequem und ohne zu viel Schreibaufwand kann man Strings einlesen, wenn man die Klasse Scanner aus dem Paket java.util benutzt. Diese Klasse ist recht praktisch, sie stellt eine Reihe von Methoden zur Verfügung, mit denen man Strings und Zahlen lesen kann. Scanner liest von einer Quelle, das kann ein anderer String sein, oder, wie in diesem Beispiel, von einem ohnehin vorhandenen InputStream-Objekt. Na, erraten Sie Spocks Kosenamen?

import java.util.Scanner;

class Kosename
{
    public static void main(String[] args) {

      Scanner zeichenScanner = new Scanner(System.in);
      System.out.print("Wie lautet der Kosename von Spock? ");
      String eingabe = zeichenScanner.next();
      if( eingabe.equals ("Spitzohr") ) {
          System.out.println("Ja, richtig.");
      }
      else {
          System.out.println("\"Spitzohr\" wäre richtig gewesen.");
      }
    }
}

Der Scanner wird mit etwas verknüpft, aus dem er lesen kann. Ähnlich wie System.out Ein Ausgabeobjekt[1] ist, so ist System.in ein Eingabeobjekt[2]. Diese Objekte stehen Ihnen immer zur Verfügung. Die Methode next() liest das nächste verfügbare Wort ein. Worte werden durch Leerzeichen getrennt. Probieren Sie aus was passiert, wenn Sie nur Return drücken.

Einen Scanner kann man mit hasNext() befragen, ob im zugehörigen Eingabekanal Zeichen vorliegen. Bei der Standardeingabe blockiert dieser Aufruf. Der Scanner wartet auf Zeichen und gibt nur dann true zurück, wenn Zeichen da sind. Auf false, wenn keine Zeichen da sind, wartet man ewig. Will man Eingabeworte auswerten und erwartet nur endlich viele Worte, dann kann man sich mit zwei Scannern behelfen. Einer liest die Eingabe komplett (mit nextLine()), der Andere (matheScanner) arbeitet auf der Eingabe des Ersten. So lässt sich leicht ein Taschenrechner schreiben, wobei Operatoren und Zahlen bei der Eingabe jeweils durch Leerzeichen getrennt werden müssen:

import java.util.Scanner;

class Rechner
{
    public static void main(String[] args) {

      int summe = 0;
      boolean erwarteZahl = true;
      boolean plus = true;

      Scanner zeilenScanner = new Scanner(System.in);
      System.out.print("Rechnung, nur +, -, keine Vorzeichen: ");
      String eingabeZeile = zeilenScanner.nextLine();

      Scanner matheScanner = new Scanner(eingabeZeile);
      while( matheScanner.hasNext() ) {
          if( erwarteZahl && matheScanner.hasNextInt() ) {
            int eingabeZahl = matheScanner.nextInt();
            summe += plus ? eingabeZahl : -eingabeZahl;
          } else if (! erwarteZahl ) {
            String operator = matheScanner.next();
            if( operator.equals("+") ) plus = true;
            else if( operator.equals("-") ) plus = false;
            else {
              System.err.println("unbekannter Operator");
              System.exit(-1);
            }
          }
          else {
              System.err.println("irgendwas ging schief");
              System.exit(-1);
          }
          erwarteZahl = !erwarteZahl;
      }
      System.out.println("Summe ist: " + summe);
    }
}

Der zeilenScanner liest wie im ersten Beispiel von der Tastatur. Seine Aufgabe ist nach zeilenScanner.nextLine() erledigt. Ab da übernimmt matheScanner, der einen String als Eingabe erhält. Solange weitere Eingabeworte vorhanden sind, wird entweder eine Zahl oder ein Operator abwechselnd eingelesen. Für diese Abwechslung sorgt die Variable erwarteZahl. Mit hasNextInt() wird geprüft, ob eine Zahl vorliegt, mit nextInt() liest der Scanner diese Zahl.

Für die Eingabe von Worten über die Tastatur ist die Scannerklasse gut zu gebrauchen. Man muss sich im dabei allerdings bewusst sein, dass hasNext(), next() und verwandte Methoden blockieren können. Das Blockieren können Sie sehen, wenn Sie in obigen Beispiel beide Scanner versuchen zu vereinen, etwa so:

public static void main(String[] args) {
      String eingabe;
      Scanner zeilenScanner = new Scanner(System.in);
      System.out.print("Eingabe: ");
      while( zeilenScanner.hasNext() ) {
            System.out.println("Lese nun Eingabe mit next().");
            eingabe = zeilenScanner.next();
            System.out.println("Eingabe war: " + eingabe);

      }
}

Das Programm beendet sich nicht, wenn die Eingabe zu Ende gelesen wurde. Viel mehr wartet hasNext(), bis wieder eine vollständige Zeile eingelesen werden kann, die mit Return abgeschlossen wurde.


Es gibt noch weitere Möglichkeiten, Texte von der Tastatur einzulesen. InputStreamReader wandelt Byte-Eingaben in Zeichen-Eingaben um. Es wirkt also als Filter. Eine der Möglichkeiten, die man mit diesem Filter hat ist, Zeichensätze darauf anzuwenden. Wir verwenden InputStreamReader lediglich als Brücke zu BufferedReader. Diese Klasse wiederum stellt einen eigenen Zwischenspeicher bei der Eingabe zur Verfügung und ist recht effizient.

import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException ;

class Sulu
{
    public static void main(String[] args) {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        System.out.print("Wie lautet der Vorname von Lieutenant Hikaru Sulu? ");
        try {
          String eingabe = br.readLine();
          System.out.println("Du hast " + eingabe + " eingegeben.");
          br.close();
        }
        catch( IOException e ) {}
    }
}

BufferedReader hat seinerseits Methoden wie readLine() und read(), um Text einzulesen. Mit close() teilt man mit, dass man keine weiteren Texte lesen möchte, man weist BufferedReader an, den Zwischenspeicher bei nächster Gelegenheit wieder freizugeben.

Kleine Anmerkung: Die Kombination aus InputStreamReader und BufferedReader wird in manchem Quelltext auch als BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) geschrieben. Beachten sie, dass beides gleichwertig ist und sich höchstens in der Lesbarkeit unterscheidet.


  1. Die zugehörige Klasse ist PrintStream.
  2. Die zugehörige Klasse ist InputStream.

Wie gibt man Strings formatiert aus?[Bearbeiten]

Sie werden sicher häufig in die Situation kommen, in einen String Zahlen einbetten zu wollen. Genau das ist das Hauptthema der String-Methode format(). Wir zeigen zunächst einmal, wie ganze Zahlen formatiert werden können. Auf eine einzelne ganze Zahl kann in einem formatierten String mehrfach zugegriffen werden, indem jedes Argument mit einem Index angesprochen wird. Bei vielen Argumenten müssen Sie also nur zählen können:

public class Kennung {

    public static void main(String[] args) {
        
        String s = String.format("Das Raumschiff mit der Kennung NCC-%1$d "
                + "hat oktal die Kennung NCC-%1$o und%nhexadezimal "
                + "NCC-%1$X.%nAls Unicode-Zeichen ist das NCC-%1$c", 1701);
        System.out.println(s);
    }
}

Das Argument ist 1701. Auf dieses Argument wird mehrfach mit 1$ zugegriffen. Hätten wir mehrere Argumente, würden wir sie mit $2 und so weiter ansprechen können. Der Formatierungsausdruck beginnt mit einem Prozentzeichen "%". Dann folgt bei Zugriffen auf Argumente die Argumentnummer. Zuletzt folgt das Konversionszeichen, im Beispiel ein o, um die oktale Darstellung des Arguments einzubinden. Um in einem Formatstring das Prozentzeichen einzubinden geben Sie %% ein, für eine neue Zeile %n.

Praktisch für Fließkommazahlen sind Argumente zur Breite und der Anzahl der Nachkommastellen. Um Ihnen die Auswirkung der Breite vorführen zu können, wird im folgenden Programm eine Lineal ausgegeben, sehen Sie selbst:

public class WbTest {

    public static void main(String[] args) {
        
        System.out.println("12345678901234567890");  // Lineal
        
        String s = String.format("%1$.2f %n%1$5.2f %n%1$10.2f", Math.PI);
        System.out.println(s);
    }
}

Nach der Argumentnummer folgt die Breite, dann ein Punkt und dann die Anzahl der Nachkommastellen. Abgeschlossen wird der Ausdruck von f für Fließkommazahlen. Die Breite bestimmt, wie breit das Argument zusammen mit den Nachkommastellen dargestellt werden sollen. Sind weniger als Breite viele Zeichen im Argument vorhanden, wird der Rest mit Leerzeichen aufgefüllt. Die Breite ist übrigens optional.

Mit Formatstrings lassen sich nicht viel mehr nützliche Dinge machen. Merken Sie sich %1$d und %1$.2f und schon haben Sie die wesentlichen Elemente immer bei der Hand.

Eine weitere Klasse namens Formatter aus dem Paket 'java.util' erweitert die Möglichkeiten um Lokalisierung.

Wie kann man einen String modifizieren?[Bearbeiten]

Die einzige richtige Antwort lautet "gar nicht", weil Strings sich nicht ändern lassen. Der Fachausdruck hierfür ist "immutable". Die in der String-Klasse vorhandenen Methoden zum Ändern von Strings geben jeweils einen neuen String zurück, bei dem die Änderung dann erfolgt ist. Die Methoden toUpperCase() und toLowerCase() erzeugen neue Strings, in Groß- oder Kleinbuchstaben. So etwas könnte ganz praktisch sein, wenn man Werkzeuge wie Quelltextformatierer baut. Viele Anwendungsfälle wird es für die Methoden aber nicht geben. Wollen Sie ein einzelnes Zeichen ändern, dann hilft sicher replace() weiter, insbesondere, wenn Scotty wieder übertreibt:

public class Scotty {

    public static void main(String[] args) {
        
            String s = "Mr. Scott, veranschlagen Sie die Reparaturzeiten übrigens immer 3-mal so lange, wie nötig?";            
            System.out.println(s.replace('3', '4'));

    }
}

Anders sieht es schon bei split() und subString() aus. Sie zerteilen einen Text in mehrere Teile, zum Beispiel Worte, oder extrahieren einen zusammenhängenden Abschnitt aus einem Text. Das ist für jede Art von Textanalyse interessant.

Mit der Methode trim() kann man dafür sorgen, dass den Text umgebende Leerzeichen entfernt werden. Praktisch für alle mit split() erzeugten Teilstrings und besonders für entgegengenommene Benutzereingaben aus Dialogen.

Zusammenführen lassen sich Strings mit concat() oder dem schon besprochenen Operator +. Falls Sie vorhaben, viele Strings zusammenzuführen, dann werfen Sie bitte einen Blick in die Dokumentation zur Klasse StringBuilder, die diese Aufgabe schneller erledigt.

Wie sucht und ersetzt man mit regulären Ausdrücken?[Bearbeiten]