Insel: Textblöcke (text blocks)

Strings mit einem Zeilenumbruch kommen in Programmen immer wieder vor, etwa bei Bildschirmausgaben, eingebettetem HTML, XML, JSON oder SQL. Ein Beispiel:

String joke =
"Lehrer: '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'\n" +
               "Schüler: 'Herr Lehrer, so viele sind wir doch gar nicht!'";

Im ersten String-Literal steht \n für den Zeilenumbruch. Das Pluszeichen setzt die beiden Strings zusammen. Der Java-Compiler stellt selbständig aus den beiden Konstanten ein größeres String Literal her.

In Java 15 wurden Textblöcke eingeführt (standardisiert im JEP 378: Text Blocks, https://openjdk.java.net/jeps/378). Damit lassen sich einfacher mehrzeilige Strings aufbauen. Drei doppelte Anführungszeichen leiten einen Textblock ein (opening delimiter genannt) und drei doppelte Anführungszeichen schließen einen Textblock wieder ab (closing delimiter genannt). Mithilfe von Textblöcken sieht unser Beispiel von oben so aus:

String joke = """
   Lehrer: '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'
   Schüler: 'Herr Lehrer, so viele sind wir doch gar nicht!'""";
System.out.println( joke );

Ein Textblock wird immer mit drei doppelten Anführungszeichen eingeleitet, und ein Zeilenumbruch muss folgen. Das bedeutet automatisch, dass die Textblöcke immer mindestens zwei Quellcodezeilen umfassen, und niemals nur in eine Zeile geschrieben werden können.

Nach der Einleitung eines Textblocks hängt der Compiler an jede Zeile, die nicht mit drei Anführungszeichen abgeschlossen wird, einen Zeilenumbruch LINE-FEED, kurs LF (Unicode \u000A). In unserem Beispiel haben wir nur einen Zeilenumbruch. Hätten wir die drei Anführungszeichen in die nächste Zeile geschrieben, hätten wir zwei Zeilenumbrüche im Ergebnis:

String joke = """
   Lehrer: '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'
   Schüler: 'Herr Lehrer, so viele sind wir doch gar nicht!'
   """;

Es macht also einen Unterschied, ob die drei Anführungszeichen zum Schließen des Textblocks in einer eigenen Zeile stehen (der entstehende String endet mit einem LF) oder am Ende eines Strings (der entstehende String endet nicht mit einem LF). Beim Schreiben würde das sicherlich etwas symmetrischer und hübscher aussehen, wenn der Beginn eines Textblocks und das Ende eines Textblocks jeweils in einer einzelnen Zeile stehen würden, doch das können wir nicht immer machen, denn das ergibt immer am Ende einen Zeilenumbruch, der vielleicht nicht gewünscht ist.

Textblöcke sind eine alternative Schreibweise für Strings in doppelten Anführungszeichen. Im Bytecode ist später nicht mehr zu erkennen, auf welche Art und Weise der String entstanden ist. Textblöcke können genauso wie reguläre Strings an allen Stellen eingesetzt werden, an denen Strings gefordert werden, etwa als Argument:

System.out.println( """
Lehrer: '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'
Schüler: 'Herr Lehrer, so viele sind wir doch gar nicht!'""" );

Ist im Quellcode innerhalb vom Textblock ein Zeilenumbruch sinnvoll, damit z. B. die Zeile nicht so lang wird, lässt sich mit einem Backslash der Zeilenumbruch im Ergebnisstring verhindern:

String joke2 = """
  Warum haben Giraffen so einen langen Hals? \
  Weil der Kopf so weit oben ist.""";
// Warum haben Giraffen so einen langen Hals? Weil der Kopf so weit oben ist.
System.out.println( joke2 );

Da Textblöcke Strings sind, lassen sie sich auch mit dem +-Operator konkatenieren:

String person1 = "Lehrer", person2 = "Schüler";
String joke = person1 +
               """
               : '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'
               """ + person2 + """
               : 'Herr Lehrer, so viele sind wir doch gar nicht!'""";
System.out.println( joke );

Es lässt sich ablesen, dass das Einsetzen von Variableninhalten nicht besonders elegant ist — das war allerdings nicht das Ziel der Textblöcke gewesen, so etwas wie String-Interpolation zu schaffen, was etwa JavaScript kann. Etwas zur Hilfe kommt die String-Methode formatted(…), die als Objektmethode hinzugekommen ist:

String person1 = "Lehrer", person2 = "Schüler";
String joke = """
   %s: '76 %% aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'
   %s: 'Herr Lehrer, so viele sind wir doch gar nicht!'""".formatted( person1, person2 );
System.out.println( joke );

Die Einrückung der Zeilen in einem Textblock spielt eine elementare Rolle. Die Zeilen eines Textblocks müssen nicht ganz links am Anfang einer Zeile bei der Position 0 beginnen, sondern dürfen rechts eingerückt sein und den üblichen Konventionen in der Einrückung von Java Programmen folgen. Bei der Einrückung sollte beiläufiger Weißraum (engl. incidental white space) aus dem eigentlichen Textblock ausgenommen werden. Hier wendet der Java-Compiler einen kleinen Algorithmus an. Die Regel ist, dass die Zeile die am weitesten links steht (die Zeile mit dem drei abschließenden Anführungszeichen gehört dazu) den beiläufigen Weißraum bestimmt, der abgeschnitten wird. Es ist dabei egal, ob das Zeichen ein Tabulator oder Leerzeichen ist, auch wenn das auf dem Bildschirm anders aussieht! Bestehen bleibt rechts von dieser Stelle der wesentliche Weißraum (engl. essential white space).

Beispiel 1:

String joke2 = """
   Warum haben Giraffen so einen langen Hals?
     Weil der Kopf so weit oben ist.""";

Ausgabe 1:

Warum haben Giraffen so einen langen Hals?

  Weil der Kopf so weit oben ist.

Beispiel 2:

String joke2 = """
   Warum haben Giraffen so einen langen Hals?
Weil der Kopf so weit oben ist.""";

Ausgabe 2:

    Warum haben Giraffen so einen langen Hals?

Weil der Kopf so weit oben ist.

Beispiel 3:

String joke2 = """
    Warum haben Giraffen so einen langen Hals?
    Weil der Kopf so weit oben ist.
""";

Ausgabe 3:

     Warum haben Giraffen so einen langen Hals?

     Weil der Kopf so weit oben ist.

Kommen wir abschließend noch zu ein paar Regeln. Im Quellcode könnte ein Zeilenende durch unterschiedliche Symbole im Text vorkommen: CR (\u000D), CR-LF (\u000D\u000A) oder LF (\u000A). Der Compiler führt eine sogenannte Normalisierung durch, dass am Ende nur ein LF (\u000A) im String steht.

Textblöcke sind keine sogenannten Raw-Strings, die „roh“ sind, sondern können alle Escape-Sequenzen, unter anderem \n, \t, \‘, \“ und \\, enthalten. So lässt sich am Ende immer noch mit \r eine CR-LF-Sequenz erschaffen:

String joke = """
   Lehrer: '76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.'\r
   Schüler: 'Herr Lehrer, so viele sind wir doch gar nicht!'""";

Das Ergebnis wäre „Lehrer: ’76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung.’\u000D\u000ASchüler: ‚Herr Lehrer, so viele sind wir doch gar nicht!'“.

Kommen im String einfache oder doppelte Anführungszeichen vor, können diese ohne Ausmaskierung eingesetzt werden. Ein Sonderfall ist allerdings, wenn das doppelte Anführungszeichen direkt am Ende vor den drei schließenden doppelten Anführungszeichen steht, dann eine Maskierung notwendig:

String joke = """
   Lehrer: "76 % aller Schüler in dieser Klasse haben keine Ahnung von Prozentrechnung."
   Schüler: "Herr Lehrer, so viele sind wir doch gar nicht!\"""";

Sollen drei Anführungszeichen selbst in der Ausgabe vorkommen, muss eines der drei Anführungszeichen ausmaskiert werden, um nicht fälschlicherweise als Abschluss interpretiert zu werden. Für die Ausgabe

1 "
2 ""
3 """
4 """"
5 """""
6 """"""

müssen wir schreiben:

System.out.println( """
                   1 "
                   2 ""
                   3 ""\"
                   4 ""\""
                   5 ""\"""
                   6 ""\"""\"""" );

Solche Zeichenketten sind nicht mehr besonders gut lesbar …

Eine weitere Regel ist, dass Leerzeichen am Ende abgeschnitten werden. Aus dem Textblock

String s = """
1␣␣␣␣␣␣␣
2␣␣␣
""";

wird der String „1\u0002“ — der “Offene Kasten” ␣ stehen hier für Leerzeichen, die man sonst im Druck nicht sehen würde.

Sollen Leerzeichen am Ende zählen, so führt man die Escape-Sequenz \s ein. Dies wird übersetzt in ein reguläres Leerzeichen \u0020. Für das obere Beispiel bedeutet das:

String s = """
          1      \s
          2  \s
          """;

Leser können mit der folgenden Aufgabe ihr Verständnis prüfen: Was für ein String entsteht?

String joke2 = """
   Warum haben Giraffen so einen langen Hals? \
   Weil der Kopf so weit oben ist.
             """;

Es entsteht „Warum haben Giraffen so einen langen Hals? Weil der Kopf so weit oben ist.\n“.

Ähnliche Beiträge

Veröffentlicht in Insel

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert