Nachdem wir im Teil 1 unserer Blogserie die Welt der String Templates und die mit dem JDK-gelieferten Prozessors STR, FMT und RAW erkundet haben, öffnen sich im Teil 2 neue Optionen. Eines der bemerkenswertesten Merkmale des String Template Features in Java 21 ist die Möglichkeit, massgeschneiderte Template-Prozessors zu erstellen.
Das Spezielle am String Template Feature ist, dass man eigene Template Processors implementieren kann. Und das Resultat muss nicht unbedingt ein String sein. Es kann ein beliebiges Objekt sein.
public interface StringTemplate {
@FunctionalInterface
public interface Processor<R, E extends Throwable> {
R process(StringTemplate stringTemplate) throws E;
}
}
Im Package java.lang gibt es neu das Interface StringTemplate mit einem Sub-Interface Processor und der zu implementierenden Methode process.
process nimmt als Input ein StringTemplate
process gibt als Return-Wert einen generischer Typ R zurück. Dies kann wie in den vorherigen Beispielen ein String sein, muss aber nicht
process kann bei der Bearbeitung eine Exception E werfen
Zum besseren Verständnis starten wir mit einem HELLO TemplateProcessor. Dieser gibt Debug Information aus und delegiert dann weiter an StringTemplate::interpolate.
Der HELLO Processor gibt die ihm zur Verfügung stehenden Daten (fragments und values) aus und delegiert via StringTemplate::interpolate weiter an die interne Implementation für die String Interpolation.
String language = "Java";
int age = 21;
System.out.println("INPUT:");
System.out.println(" { language }_ist_{ age }_geworden!");
String infoJava21 = HELLO. "{ language }_ist_{ age }_geworden!" ;
System.out.println("RESULT:");
System.out.println(" " + infoJava21);
Nun kann man sehen, was beim Anfangsbeispiel genau passiert (zur Verdeutlichung sind beim Input Spaces durch „_“ ersetzt): Die Inputs sind:
2 Variablen: language vom Typ String und age vom Typ int
Das StringTemplate mit Embedded Expressions { language }ist{ age }_geworden!.
Aus dem Input werden die Strings zwischen den Embedded Expressions extrahiert und damit die fragments Liste befüllt. Die values Liste enthält die ausgewerteten Emedded Expression. Schliesslich mischt der Aufruf von StringTemplate::interpolate die fragements und values zu einem neuen String.
INPUT:
{ language }_ist_{ age }_geworden!
HELLO:
fragments : , _ist_, _geworden!
values : Java, 21
value types: String, Integer
RESULT:
Java_ist_21_geworden!
Mit diesem Vorwissen kann man relativ einfach das Verhalten von STR durch folgende Schritte nachbauen:
extrahieren der values() in eine mutable Liste values
einen StringBuilder für das Resultat defnieren
über die fragments iterieren
wenn das fragment nicht leer ist, es zum Output hinzufügen
wenn die values Liste nicht leer ist, das erste Element daraus nehmen (und in der Liste löschen) und zum Output hinzufügen
und dann den StringBuilder als String zurückgeben
PS: Dem aufmerksamen Leser ist eventuell aufgefallen, dass es im Code in List::removeFirst gibt. Ja, das ist auch neu in Java 21 und gehört zum Feature Sequenced Collections (JEP 431). Dies werden wir in einer der nächsten Folgen näher anschauen.
public static StringTemplate.Processor<String, RuntimeException> MY_STR = (StringTemplate template) -> {
// convert values to a mutable list
List