Text Blocks Java 13 – czyli bloki tekstu w kodzie

  • Michał 
  • Java

Text Blocks to jedna z nowych funkcjonalności która weszła jako preview w Javie 13. Oznacza to, że jest to nowy element języka, w pełni wyspecyfikowany, w pełni zaimplementowany, ale może zostać jeszcze zmieniony po informacji zwrotnej od developerów którzy zaczną z niej korzystać.

O blokach tekstu wspomniałem już w poprzednim artykule o wszystkich nowościach w Javie 13: https://blog.mloza.pl/java-13-nowe-funkcjonalnosci-szczesliwej-trzynastki/. W tym poście postaram się napisać szerzej i podać więcej przykładów wykorzystania. Cała specyfikacja jest dostępna tutaj: JEP 335.

Bloki tekstu ułatwiają pracę z wielolinijkowymi Stringami. Nie trzeba ich konkatenować żeby dobrze wyglądały i nie psuły czytelności kodu. Nie musimy dodawać specjalnych znaków do łamania linii. Nasz kod też będzie lepiej wyglądał ponieważ możemy dodać wcięcia w kodzie które zostaną usunięte w preprocesingu. Jeśli programowałeś w innych językach (np. w Pythonie) to powinieneś być zaznajomiony z tą koncepcją.

Jak wspomniałem wcześniej, jest to dopiero preview, więc aby przykłady zaczęły działać należy do linii komend dodać –enable-preview. Inaczej dostaniemy błąd kompilacji. Jeśli używasz Mavena możesz to łatwo skonfigurować w pom.xml:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>13</release>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
view raw enable-preview.xml hosted with ❤ by GitHub

Potem przy uruchomieniu podajemy również jako argument –enable-preview:

java --enable-preview -classpath target/classes pl.mloza.TextBlock

Pierwszy przykład

package pl.mloza;
public class TextBlocks {
private static String classic = " <html>\n" +
" <head>\n" +
" <title>Hello world!</title>\n" +
" </head>\n" +
" <body>\n" +
" <h1>Hello World</h1>\n" +
" </body>\n" +
" </html>";
private static String block = """
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
""";
public static void main(String[] args) {
System.out.println(classic);
System.out.println(block);
}
}
view raw TextBlocks.java hosted with ❤ by GitHub

W powyższym przykładzie możesz porównać jak wygląda klasyczny string wielolinijkowy z blokiem tekstu który znajduje się poniżej. Użycie nowego podejścia wygląda dużo bardziej estetycznie, nie mamy takiej ilości cudzysłowów, ani nie musimy wstawiać znaków nowej linii na końcu każdego wiersza. Kod jest dużo czystszy. Do tego możemy łatwo manipulować jak duże wcięcie ma być pozostawione w kodzie, ale o tym będzie więcej w dalszej części.

Możliwości bloków tekstu

Zobaczmy więcej przykładów które omówię z chwilę.

public class TextBlocksTwo {
// #1
private static String noLineEnding = """
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>""";
// #2
private static String block = """
"We can put here quotes but slashes needs to be escaped \\"
Comments are not working here # // /* xxx */
Triple quotes needs to be escaped \"""
""";
// #3
private static String emptyString = """
""";
// #4
private static String blockConcatenation = block + noLineEnding;
// #5
private static String escapeSequences = """
First line \n Second line
""";
public static void main(String[] args) {
System.out.println(noLineEnding);
System.out.println(block);
System.out.println("Empty string: " + emptyString);
System.out.println(blockConcatenation);
System.out.println(escapeSequences);
}
}
view raw TextBlocksTwo.java hosted with ❤ by GitHub

Zacznijmy od przykładu oznaczonego jako #1, jeśli nie chcemy na końcu znaku nowej linii możemy umieścić potrójny cudzysłów zaraz za tekstem.

W #2 przykładzie możemy zobaczyć, że możemy używać bez problemu cudzysłowów oraz znaków komentarzy i nie musimy ich poprzedzać slashem. Jednak ukośnik musimy poprzedzić kolejnym ukośnikiem jako, że sekwencje ucieczki są wciąż parsowane. Możemy to wszystko zobaczyć w przykładzie #5 gdzie umieszczenie \n łamie linię. Podobnie, jeśli chcemy dodać potrójny cudzysłów to musimy go poprzedzić ukośnikiem (ale wystarczy jednym).

Jeśli chcemy stworzyć pusty blok, musimy rozbić go na dwie linie. Tutaj lepiej sprawdzi się klasyczny String.

W przykładzie numer #4 widzimy, że możemy bloki łączyć jak klasyczne stringi używając znaku plusa.

Po procesie kompilacji, bloki tekstu są traktowane jak zwykłe Stringi i nie jest zapisywana żadna informacja czy dany ciąg znaków pochodzi z bloku czy z klasycznego Stringa.

Wcięcia

Tutaj jest dość prosto. Ilość spacji która nie zostanie obcięta jest definiowana przez dolny potrójny cudzysłów lub najbardziej wysunięta w lewo linia. Zobaczmy to na przykładzie.

public class TextBlocksIndentation {
// #1
private static String indentation = """
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
""";
// #2
private static String indentationTwo = """
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
""";
public static void main(String[] args) {
System.out.println(indentation);
System.out.println(indentationTwo);
}
}
view raw Indentation.java hosted with ❤ by GitHub

W przykładzie numer #1 wynikowy string będzie poprzedzony przez cztery spacje. Dzieje się tak ponieważ zamykające cudzysłowy są przesunięte o tyle w lewo. Przykład drugi ilustruje co się stanie gdy je przesuniemy dalej niż tekst. Wtedy to tekst będzie definiował ilość spacji poprzedzających, czyli zero ponieważ tekst jest wysunięty najbardziej w lewo.

Formatowanie

Niestety bloki tekstu nie wspierają interpolacji stringów, czyli podstawiania wartości zmiennych w łańcuchach znaków. Zamiast tego możemy użyć nowej funkcji String::formatted(Object… args) która podstawia wartości obiektów w odpowiednie miejsca w bloku. Różni się ona od istniejącej funkcji String::format(String format, Object… args) tym, że tutaj nie przekazujemy już naszego Stringa w parametrze tylko wywołujemy ją na instancji obiektu. Poza tym implementacja jest identyczna. Przykład poniżej.

package pl.mloza;
public class TextBlocksFormatting {
private static String indentation = """
<html>
<head>
<title>%s</title>
</head>
<body>
<h1>%s</h1>
</body>
</html>
""";
public static void main(String[] args) {
System.out.println(indentation.formatted("Hello title", "Hello header"));
}
}
view raw Formatting.java hosted with ❤ by GitHub

Możemy też zamiast tego po prostu zrobić konkatenację ze zmiennymi jak w zwykłym stringu.

public class TextBlocksConcatenation {
public static void main(String[] args) {
String title = "Hello title";
String header = "Hello header";
System.out.println("""
<html>
<head>
<title>""" + title + """
</title>
</head>
<body>
<h1>""" + header + """
</h1>
</body>
</html>
""");
}
}
view raw Concatenation.java hosted with ❤ by GitHub

Jednak używanie tego w ten sposób połączone z koniecznością złamania linii po potrójnym cudzysłowie psuje estetykę kodu oraz wynikowy string może mieć niepoprawne wcięcia przez to.

Co nie można robić

Na koniec jeszcze kilka przykładów niepoprawnego użycia bloków tekstu.

public class TextBlocksInvalid {
// #1
private static String invalidOne = """""";
// #2
private static String invalidTwo = """ """;
// #3
private static String invalidThree = """
";
// #4
private static String invalidFour = """
asd \ asd
"""";
}
view raw Invalid.java hosted with ❤ by GitHub

Zaczynając od początku, numery #1 i #2 reprezentują ten sam błąd, brak złamania linii po rozpoczynającym cudzysłowie. Kolejny, numer #3, zamknięcie przez pojedynczy cudzysłów. Numer #4, pojedynczy ukośnik, powinien być poprzedzony drugim ukośnikiem aby nie powodować błędów.

Podsumowanie

Bloki tekstu są świetnym usprawnieniem języka. Dzięki nim będziemy mogli w dużo czytelniejszy sposób osadzać większe kawałki tekstu lub nawet innych języków jak na przykład kwerendy do SQLa czy JavaScripta do ewaluowania przez silnik JS.

Brakuje niestety interpolacji która by znacząco ułatwiła osadzanie zmiennych w ciągach znaków. Dostaliśmy jedynie funkcję formatted która może pomóc w tym przypadku, aczkolwiek nie jest to najlepsze rozwiązanie problemu. Może w kolejnych wersjach doczekamy się takiego usprawnienia które jest znane z innych języków, jak chociażby Kotlin.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *