Łańcuchy szablonowe - String Templates

Przez długi czas formatowanie tekstu w Javie było możliwe wyłącznie z wykorzystaniem Formattera, który najczęściej był wykorzystywany pośrednio przez metodę String.format(), albo System.out.printf(). Wiele języków programowania udostępniało jednak znacznie wygodniejszy mechanizm interpolacji tekstu, czyli prostej podmiany fragmentów literału znakowego na wynik wyrażeń w nich zawartych.

Twórcy Javy postanowili wprowadzić podobny mechanizm w Javie 21, jednak w nieco bardziej elastycznej wersji, która jest jednocześnie wstecznie kompatybilna, zapewnia wysoki poziom bezpieczeństwa i daje możliwości rozbudowy poprzez procesory szablonów.

Wprowadzenie

Niektóre języki programowania dają możliwość interpolacji tekstu, czyli wykonania wyrażeń zawartych w literale znakowym i np. podmianę wybranych fragmentów na wartości przypisane do zmiennych. Mówiąc prościej, w Stringach mogą znajdować się specjalne wyrażenia, które zostaną wykonane w trakcie ich interpretacji i podmienione na wynik tych wyrażeń.

W języku JavaScript można zapisać np. taki fragment kodu:

let name = 'John';
let age = 25;
let msg = `Hello ${name}. Next year you will be ${age + 1} years old`;
console.log(msg);

W wyniku jego wykonania w konsoli zostanie wyświetlony tekst:

Hello John. Next year you will be 26 years old

Interpolowany tekst musi być zapisany między backtickami, czyli wstecznymi apostrofami, natomiast jeżeli chcemy do niego wstawić wartość zmiennej, albo wykonać proste wyrażenie, to można to zrobić wykorzystując fragment ${expr}.

W języku Java nie istniał podobny mechanizm, aż do Javy 21. Do dyspozycji mieliśmy jedynie klasę Formatter dodaną w Javie 1.5, która pozwala osiągnąć podobny efekt, jednak w nieco bardziej skomplikowany sposób, ponieważ wymaga zdefiniowania szablonu i przekazania do niego argumentów podczas wywołania metody format().

Przytoczony wcześniej fragment kodu w Javie wyglądałby tak:

var name = "John";  
var age = 25;  
Formatter formatter = new Formatter();  
var template = "Hello %s. Next year you will be %d years old";  
System.out.println(formatter.format(template, name, age + 1));

W Javie wymagany jest import klasy Formatter, utworzenie jej instancji, utworzenie szablonu z odpowiednimi specyfikatorami, a także wywołanie metody format() z odpowiednimi argumentami. Najczęściej nie używa się klasy Formatter bezpośrednio, tylko zamiast niej wykorzystujemy metodę format() z klasy String, albo w przypadku drukowania czegoś w konsoli możemy wywołać metodę printf().

Wcześniejszy kod można więc uprościć do takiej wersji:

String name = "John";  
int age = 25;  
String template = "Hello %s. Next year you will be %d years old%n";  
System.out.printf(template, name, age + 1);

Choć sam zapis zajmuje teraz tyle miejsca co wersja z języka JavaScript, to jednak konieczność definiowania szablonu ze specyfikatorami i konieczność jawnego podstawiania odpowiednich argumentów sprawia, że nie jest to tak czytelne. Wygodniej byłoby odwoływać się do nazw zmiennych bezpośrednio w samym literale.

String templates

Twórcy Javy mogli po prostu skopiować interpolację napisów z innych języków programowania, jednak jak to w Javie bywa, najpierw postanowili sobie zadać pytanie, czy nie da się istniejących rozwiązań ulepszyć. Interpolacja, którą pokazałem Ci na przykładzie języka JavaScript, jest wygodna, ale jednocześnie niebezpieczna. Jeżeli założymy np., że w Javie istniałby mechanizm analogiczny do tego z JavaScriptu, to istnieje niezerowe prawdopodobieństwo, że ktoś prędzej, czy później zacząłby wykorzystywać łańcuchy szablonowe w taki sposób:

String userId = ...
String sql = "SELECT * FROM user WHERE id = ${userId}";

Wprawne oko szybko zauważy, że jeżeli identyfikator użytkownika byłby przekazywany przez klienta, to jest to typowy przykład podatności na SQL injection. Z tego powodu w Javie postanowiono rozwiązać to w nieco inny sposób, wprowadzając procesory szablonów (template processor), które pozwalają na walidację przekazanych argumentów przed podstawieniem ich do szablonu.

Spójrzmy teraz, jak można wykorzystać nową funkcjonalność w praktyce. Podstawowym pojęciem, którym będziemy się posługiwali, jest wyrażenie szablonowe (ang. template expression), które schematycznie wygląda w ten sposób:

PROCESOR_SZABLONU."szablon"

Procesory tekstu to klasy implementujące interfejs StringTemplate.Processor. Istnieją dwa predefiniowane procesory:

  • STR - standardowy procesor, który pozwala na podstawianie zmiennych, wywoływanie metod, i wykonywanie wyrażeń w ramach szablonu.
  • FMT - procesor formatujący, w którym możemy używać specyfikatorów tak, jak w metodzie String.format(). Wracając do wcześniejszego przykładu z imieniem i wiekiem, to od Javy 21 możemy zastosować następujący zapis:
String name = "John";  
int age = 25;  
System.out.println(STR."Hello \{name}. Next year you will be \{age + 1} years old");

W wyniku otrzymamy:

Hello John. Next year you will be 26 years old

Jeżeli zależałoby nam dodatkowo na formatowaniu tekstu, to należy wykorzystać procesor formatujący FMT, np.:

import static java.util.FormatProcessor.FMT;
...
String name = "John";  
double salary = 123.456;  
System.out.println(FMT."Hello \{name}. your salary is: %.2f\{salary}");

Statyczny import do procesora STR dodawany jest w każdej klasie automatycznie. W przypadku procesora FMT lub własnych procesorów taki import należy dodać samodzielnie. Wynikiem powyższego fragmentu kodu będzie wydruk:

Hello John. your salary is: 123.46

Kompatybilność wsteczna

Wiele osób narzeka na to, że w Javowej wersji wyrażenia w ramach szablonów zapisywane są jako \{expr}, a nie najbardziej powszechna w innych językach programowania wersja z symbolem dolara ${expr}. Nie jest to jednak widzimisię twórców Javy, tylko świadoma decyzja podyktowana kompatybilnością wsteczną.

Napis "${something}" jest w starszych wersjach Javy prawidłowym literałem, który nie oznacza nic konkretnego. Jeżeli ktoś używał w swoim kodzie tego typu zapisów i postanowiłby uruchomić swój kod z nowszą wersją Javy, to mogłoby się okazać, że kod ten się nie skompiluje. Z kolei String "\{something}" przed Javą 21 jest niepoprawny i powoduje błąd kompilacji, więc mamy gwarancję, że nikt czegoś takiego w swoim kodzie nie wykorzystywał i nie ma ryzyka, że otrzymamy nieoczekiwane zachowanie.

Więcej informacji o String templates znajdziesz na oficjalnej stronie OpenJDK: https://openjdk.org/jeps/430

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.