Java ist 21: String Templates (Teil 1/2)

Java 21 bringt String Interpolation in Form von String Templates als Preview Feature. Erfahre, wie du diese neue Funktion nutzen kannst, obwohl sie noch nicht final ist und spezielle Compiler-Flags erfordert.

Wir starten diesmal mit einer Frage. Welches Feature hat Java nicht, das andere moderne Sprachen schon längst bieten? Es ist die String Interpolation («interpolation of expressions that are embedded inside a string»). Mit Java 21 bekommt Java unter dem Namen String Templates auch String Interpolation (PEP 430: String Templates (Preview)). Einziger Wermutstropfen: es ist in Java 21 noch ein Preview Feature, d.h. die Implementation ist nicht final und kann sich in den nächsten Versionen noch ändern. Zudem muss man Java mit speziellen Flags kompilieren und ausführen (How to Enable Java Preview Features).

Was sind String Templates

In der Programmierung hat man oft das Problem, dass man einen String aus anderen Strings und dynamischen Teilen (Variablen, Expressions) zusammensetzen möchte. Ein kleines Beispiel macht das deutlich: Wir möchten «Java ist 21 geworden!» als String haben, wobei «Java» und «21» jeweils in Variablen abgelegt sind.

String language = "Java";
int age = 21;

String info = language + " ist " + age + " geworden!";
System.out.println(info);

In Java macht man das traditionell mit dem «+» Operator. Alternativen sind StringBuilder/StringBuffer oder String::format/String::formatted. Aber alle haben den gleichen Nachteil: viel Code und nicht sehr leserlich. Mit Java 21 kann man das neu als Template Expression schreiben. Dies würde dann folgendermassen aussehen.

String language = "Java";
int age = 21;

String infoJava21 = STR. "\{ language } ist \{ age } geworden!" ;
System.out.println(infoJava21);

Template Expressions

Alles rechts von «=» ist eine Template Expression, die aus folgenden Teilen besteht:

  • dem Template Processor (STR)
  • einem «.»
  • einem Template Argument

Ein Template Processor ist eine Implementation des (Functional) Interfaces StringTemplate.Processor mit der zu implementierenden Methode process. In unserem Beispiel ist das der Template Processor STR, der mit Java 21 mitgeliefert wird.

Dem Template Processor muss ein «folgen. Dies ist von den Java Designern so vorgegeben und soll an einen Methodenaufruf erinnern, quasi: STR.process(templateArgument).

Das Template Argument kann mehrere Ausprägungen haben:

  • ein String Template
  • ein Text Block Template
  • ein (normales) String Literal
  • ein (normaler) Text Block

Ein String Template ist ein String Literal mit Embedded Expressions (hier wären das \{ language } und \{ age }). Jede Expression muss dabei in «\{}» eingebettet werden. Und in Embedded Expressions sind Kommentare und Zeilenumbrüche erlaubt.

PS: Nur so am Rande. «\{» wurde vermutlich gewählt, weil es eine nicht gültige Zeichenkombination in Java Strings ist.

Ein Text Block Template ist ein Textblock (d.h. Java Multiline String) mit Embedded Expressions.

(Die Klasse) StringTemplate

Ein String Template bzw. Text Block Template sieht für uns von aussen aus wie ein String bzw. String Literal mit Platzhaltern. Aber es ist kein String, sondern ein Objekt vom Typ StringTemplate. Und ein StringTemplate kann ohne Template Processor nicht existieren, d.h. man kann kein StringTemplate definieren und einer Variablen zuordnen. Man würde stattdessen eine Fehlermeldung erhalten. (Processor missing from string template expression).

Ein StringTemplate enthält die aufbereiteten Daten des «Input Literals mit Embedded Expressions». Und es hat mehrere Methoden, um auf diese Daten zuzugreifen bzw. weiterzuverarbeiten:

  • fragments(): Liste der Strings zwischen den Embedded Expressions
  • values(): Liste der einzelnen Werte der ausgewerteten Embedded Expressions
  • interpolate(): die eigentliche String Interpolation, die aus den Fragments und Values einen neuen String zusammensetzt.

Wenn ein String Template ausgewertet wird («evaluate»), passiert Folgendes:

  • Die process Methode des Template Processors wird aufgerufen mit dem StringTemplate als Argument.
  • Der Template Processor hat Zugriff auf die Fragments und Values und kann mittels interpolate() das StringTemplate in einen String umwandeln.

(Im Java 21) Vordefinierte Template Processors

Java 21 bringt 3 vordefinierte Template Processors mit:

  • STR
  • FMT
  • RAW

STR: StringTemplate -> String
STR haben wir bereits gesehen. Es ist ein Template Processor, der ein StringTemplate in einen String umwandelt. Speziell ist, dass er jedem Java Programm direkt ohne Import zur Verfügung steht.

  • Er ist definiert in java.lang.StringTemplate
  • Java macht im Hintergrund ein automatisches «import static java.lang.StringTemplate.STR»

Machen wir doch ein Beispiel mit dem STR und Text Blocks. Als Vorbereitung für die weiteren Beispiele erstellen wir uns ein Record «ProgrammingLanguage», der Informationen über Programmiersprachen beinhaltet: Name, aktuelle Version, und das Ranking (wie verbreitet/beliebt ist die Sprache).

public record ProgrammingLanguage(String name, int version, int ranking) {
}

PS: Wer nicht vertraut ist mit Records: ein Record ist ein immutable Container für Daten. Getter, equals, hashCode und toString werden dabei automatisch generiert. Es gibt dazu bereits 2 Blogartikel von Puzzle [Java Records, Java Records 2].

Wir erstellen uns einen Array mit 3 beliebten Programmiersprachen: Java, Python und JavaScript.

var langs = new ProgrammingLanguage[] {
        new ProgrammingLanguage("JavaScript", 13, 1),
        new ProgrammingLanguage("Python", 12, 2),
        new ProgrammingLanguage("Java", 21, 3)
};

Unser Ziel: wir möchten einen JSON String haben, mit den Infos zur Programmiersprache Java. Das Resultat sollte ungefähr so aussehen:

{
    "languages": [
        {
            "name": "Java",
            "version": 21,
            "ranking": 3
        }
    ]
}

Starten wir mal mit einer Version nur mit Strings. Das macht nicht wirklich Spass. Man muss sich manuell um Zeilenumbrüche und Escaping (der « ») kümmern und es sind String Concatinations mit «+» nötig. Der Code wird dadurch nicht gut lesbar.

String myDailyLanguage =
        "{\n" +
        "    \"languages\": [\n" +
        "        {\n" +
        "            \"name\": \"" + langs[2].name()     + "\",\n" +
        "            \"version\": " + langs[2].version() + ",\n" +
        "            \"ranking\": " + langs[2].ranking() + "\n" +
        "        }\n" +
        "    ]\n" +
        "}\n";

System.out.println(myDailyLanguage);

Mit Text Block und String Interpolation kann man das einfacher und viel lesbarer hinbekommen: manuelle Zeichenumbrüche, Escaping (einzig die String Werte muss man immer noch manuell escapen) und String Concatinations fallen weg. Das Resultat ist ein valider JSON String.

String myDailyLanguage = STR. """
{
    "languages": [
        {
            "name": "\{ langs[2].name() }",
            "version": \{ langs[2].version() },
            "ranking": \{ langs[2].ranking() }
        }
    ]
}
""" ;

System.out.println(myDailyLanguage);

FMT: StringTemplate -> String

Der FMT ist wiederum ein Processor, der ein StringTemplate in einen String umwandelt und String Interpolation macht (analog dem STR). Zusätzlich kann man noch angeben, wie die ausgewerteten Embedded Expressions formatiert werden sollen. Es können die gleichen Formatierungen wie bei
java.util.Formatter verwendet werden.

Wichtig: die Formatierung muss direkt vor der Embedded Expression angegeben werden. Dazu ein Beispiel:

Wir haben die 3 populären Sprachen und möchten mit diesen Infos einen Report mit Spalten generieren: ein «%-12s» und ein «%3d» bzw. «%4d» lösen das Problem und wir erhalten drei Spalten. Definiert ist FMT in java.util.FormatProcessor.FMT und muss manuell importiert werden.

String report = FMT. """
    Name         Age Ranking
    %-12s\{ langs[0].name() } %3d\{ langs[0].version() } %4d\{ langs[0].ranking() }
    %-12s\{ langs[1].name() } %3d\{ langs[1].version() } %4d\{ langs[1].ranking() }
    %-12s\{ langs[2].name() } %3d\{ langs[2].version() } %4d\{ langs[2].ranking() }
    """ ;

System.out.println(report);
Name         Age Ranking
JavaScript    13    1
Python        12    2
Java          21    3

RAW: StringTemplate -> StringTemplate
RAW ist ein Template Processor, der das Input StringTemplate ohne Veränderung wieder zurückgibt. Definiert ist RAW in java.lang.StringTemplate.RAW und muss manuell importiert werden.

Interessiert dich das Thema und möchtest du mehr zu RAW erfahren? Im nächsten Blogartikel berichte ich mehr dazu.

Fazit

Es hat lange gedauert … aber nun hat auch Java String Interpolation und das Resultat ist grossartig. Einziger Wermutstropfen ist, dass String Templates in Java 21 (nur) ein Preview Feature sind, das sich in der Zukunft noch ändern kann.

Wir haben uns in diesem Teil 1 des Blogartikels die Grundlagen der String Templates und die mit dem JDK gelieferten Processors STR, FMT und RAW angeschaut. Im kommenden Teil 2 werden wir sehen, wie man eigene Processors definieren kann.

Puzzle Blogs über Records

Java Records
Java Records 2

Kommentare sind geschlossen.