Groovy kennt zwei Arten von Strings:
- den klassischen Java-String vom Typ java.lang.String. Diese sind dir sicherlich aus der Java-Welt schon bekannt.
- und darüber hinaus den GString, einen Groovy-spezifischen String, der einen besonderen Trick beherrscht, die „Interpolation“. Dazu gleich mehr.
- Wichtig sind auch mehrzeilige Strings im Quellcode, markiert mit Triple-Quotes
GStrings erzeugen
Ob eine Zeichenkette ein Java-String oder ein GString wird, hängt von drei Dingen ab:
- von den Anführungszeichen:
- Single-Quotes (
'...'
) erzeugen immer einen Java-String. - Double-Quotes (
"..."
) können einen GString erzeugen, sobald Interpolation ins Spiel kommt. Ansonsten entsteht auch hier ein Java-String
- Single-Quotes (
- vom Inhalt:
- Wenn der Text keine Interpolation enthält, dann wird ein ganz normaler Java-String erzeugt.
- Sobald du eine Variable oder einen Ausdruck in
...${variable}...
einbaust, wird daraus ein GString.
- vom Typ des Ergebnisses:
- Eine Variablendeklaration mit def kann einen GString erzeugen.
- Eine Variablendeklaration mit String erzwingt unmittelbar die Erzeugung eines Strings.
Schauen wir uns ein paar Beispiele an:
Doppelte Anführungsstriche aber keine Variable
def someString = "Hallo" println( someString.class.getName() ) // => java.lang.String
Jetzt erzwingen wir die Interpolation
String username = 'Heiko' def stringInterpolation = "Hallo ${username}" println stringInterpolation println 'Klassenhierarchie von stringInterpolation' def clazz = stringInterpolation.getClass() while (clazz != null) { println clazz.name clazz = clazz.superclass }
Die Ausgabe lautet
Hallo Heiko
Klassenhierarchie von stringInterpolation
org.codehaus.groovy.runtime.GStringImpl
groovy.lang.GString
groovy.lang.GroovyObjectSupport
java.lang.Object
Wir entdecken dabei, dass stringInterpolation sogar eine Unterklasse von GString ist.
Bei der Ausgabe zeigt sich auch die durchgeführte Interpolation.
Aber was passiert, wenn wir ausdrücklich einen Java-String bestellen?
String username = 'Heiko' String stringInterpolation = "Hallo ${username}" println stringInterpolation println stringInterpolation.class.getName()
Das Ergebnis sieht jetzt so aus:
Hallo Heiko
java.lang.String
Wir hatten offenbar kurz einen GString. Dieser wurde aber gleich wieder plattgebügelt, weil stringInterpolation ausdrücklich ein java.lang.String sein soll.
Halten wir fest:
Wenn du einen GString behalten willst, dann ist die richtige Aufbewahrungsart eine Variablendeklaration mit def.
Wann wird die Interpolation eines GStrings ausgewertet?
Nach einigen Experimenten mit der Interpolation von GStrings kam ich auf ein überraschendes Ergebnis. Das hier geht nicht, obwohl ich erwartet hätte, das es geht.
String username = 'Heiko' def stringInterpolation = "Hallo ${username}" println stringInterpolation username = "Evermann" println stringInterpolation
Was ist jetzt die Ausgabe?
Hallo Heiko
Hallo Heiko
Oh. Das war überraschend, oder?
Nach einiger Recherche zeigt sich: der GString cacht das Ergebnis und sieht keine Notwendigkeit, seinen Wert neu zu berechnen. Man muss die „lazy evaluation“ daher erzwingen und dem GString gegenüber behaupten, hier wäre eine „closure“ auszuwerten. Eine Closure ist so etwas wie eine anonyme Funktionsdefinition. (Mehr dazu später einmal in einem eigenen Artikel.)
Bauen wir das also auf Closure um, im direkten Vergleich:
println "Test mit Heiko" String username = 'Heiko' def eagerGString = "Hallo $username" def lazyGString = "Hallo ${->username} " println eagerGString println lazyGString println "-----------------" println "Test mit Evermann" username = 'Evermann' println eagerGString println lazyGString
Die Ausgabe lautet jetzt:
Test mit Heiko
Hallo Heiko
Hallo Heiko
-----------------
Test mit Evermann
Hallo Heiko
Hallo Evermann
Man sieht: mit Closure geht es, ohne Closure geht es nicht.
Eine Suche in der Groovy-Dokumentation zeigt: Im Kleingedruckten wird das auch genau so erklärt.
Mehrzeilige Strings: Die Triple-Quotes
Manchmal braucht man im Quelltext mehrzeilige Stringdefinitionen. Mit dreifachen Anführungsstrichen, auf Englisch triple quotes geht das direkt und ohne Klimmzüge.
Hier ein Beispiel:
def multilineString = """ Dies ist eine mehrzeilige Zeichenkette """ println multilineString
Die Ausgabe ist dann
Dies ist
eine mehrzeilige
Zeichenkette
Groovy behält sämtliche Zeilenumbrüche und Leerzeichen zwischen den dreifachen Anführungszeichen.
Mit dreifachen doppelten Anführungszeichen kannst du einen GString mit Interpolation erzeugen. Auch hierzu ein Beispiel:
def name = "Heiko" def multilineGreeting = """ Hallo ${name}, wie geht's dir heute? """ println multilineGreeting
Die Ausgabe lautet dann:
Hallo Heiko,
wie geht's dir heute?
Falls keine Interpolation im String enthalten ist, gilt auch hier: in diesem Falle bekommst du einen mehrzeiligen normalen java.lang.String
Der Vorteil der triple-quoted Strings
Mehrzeilige Strings im Quelltext machen dein Programm übersichtlicher. Dank Triple-Quotes kannst du umfangreiche Texte (z. B. JSON, Markdown, mehrzeilige Logmeldungen, etc.) lesbar in den Code einfügen – mit allen Zeilenumbrüchen und Einrückungen.
stripIndent: mehrzeilige Einrückungen korrigieren
Wenn du längere Texte mehrzeilig im Quellcode stehen hast, dann willst du diese vielleicht einrücken, so weit wie deine Einrückung gerade im Quelltext steht. Andererseits soll der resultierende String diese Leerzeichen am Zeilenanfang vielleicht nicht haben.
Hier hilft die String-Methode stripIndent. Sie steht sowohl bei java.lang.String als auch beim GString zur Verfügung.
Die Idee ist: stripIndent schaut sich alle Zeilen des Strings an und entfernt in allen Zeilen gleich viele führende Leerzeichen. Die Zeile mit den wenigsten Leerzeichen legt damit fest, wieviele Zeichen entfernt werden können.
def text = """\ Zeile 1 Zeile 2 Zeile 3\ """.stripIndent() println("=======") println text println("=======")
Die Ausgabe lautet jetzt
=======
Zeile 1
Zeile 2
Zeile 3
=======
Bitte bechte die Backslashes. Sie gehören zur Syntax der triple-quoted Strings. Sie besagen: diese Zeile bitte mit der folgenden zusammenziehen.
Ohne Backslash wären die erste und die letzte Zeile, also diejenigen, in denen die dreifachen Anführungszeichen stehen, jeweils eine eigene Zeile, aber ohne Inhalt, ohne Leerzeichen. Und stripIndent würde dann schließen: OK, die niedrigste Zahl von führenden Leerzeichen über den gesamten Text ist 0. Und dann würde die Einrückung nicht so korrigiert werden, wie du es wolltest.
stripMargin: explizites Löschen von führenden Leerzeichen
Etwas anders funktioniert die Methode stripMargin. stripMargin sucht nach führenden Leerzeichen, abgeschlossen mit dem senkrechten Strich |. Bis dorthin wird die Zeile gelöscht.
Schauen wir uns das an einem Beispiel an. Hier habe ich den | nur in einer einzigen Zeile verwendet, um den Effekt deutlicher zu machen.
def text = """\ Zeile 1 |Zeile 2 Zeile 3\ """.stripMargin() println("=======") println text println("=======")
Die Ausgabe lautet
======
Zeile 1
Zeile 2
Zeile 3
=======
Die Zeile 2 hat ihre Einrückung verloren.
Was kann man damit sinnvolles machen? Du kannst einen längeren XML-Text schreiben, diesen für dich lesbar einrücken, aber die Ausgabe des Programms hat diese Einrückungen nicht. Technisch braucht XML die Leerzeichen nicht. Sie dienen nur dem menschlichen Leser.
Arbeiten mit Strings in Groovy
Wenn es um das grundlegende Arbeiten mit Strings geht, dann unterscheiden sich Groovy und Java fast gar nicht.
Die Länge eines Strings mit Groovy ermitteln
Groovy bietet hier zwei Methoden an. Aus Java bekannt ist length()
. Dieselbe Funktion ist in Groovy auch als size()
zu bekommen. Dies wurde eingeführt, weil es viele andere Fälle in Groovy gibt, in denen mit size() gearbeitet wird. Und so wollte man das auch hier ermöglichen.
Hier ein Beispielcode:
def str = "Groovy" println str.length() // 6 println str.size() // 6
In Groovy auf Leerstring prüfen
Die Methode str.isEmpty()
ist true, wenn der String keinerlei Zeichen enthält, also dann, wenn die Länge = 0 ist.
def emptyStr = "" println emptyStr.isEmpty() // true
Daneben gibt es noch die Methode str.isBlank()
, diese prüft, ob der String leer ist oder nur Leerzeichen enthält.
def onlySpaces = " " println onlySpaces.isBlank() // => true
Strings in Groovy auf Gleichheit prüfen
Beim Test auf Gleichheit von zwei Strings verhält sich Groovy anders als Java.
Betrachten wir dieses Java-Beispiel:
package de.evermann.java; public class JavaEquals { public static void main(String[] args) { String s1 = "abc"; String s2 = "a"; s2 += "bc"; System.out.println(s1.equals(s2)); // true System.out.println( s1 == s2); // false } }
Aus Sicht von Java sind s1 und s2 verschiedene Objekte. Der Operator == vergleicht aber auf Identität der Objekte, nicht auf gleichen Inhalt. Also ist s1 == s2
nicht true
. Um das Beispiel zu konstruieren, musste ich den Java-Compiler überlisten. ein einfachess2 = "abc"
wäre vom Compiler optimiert worden: gleiche Textkonstante bedeutet gleiches Objekt. Dadurch, dass ich s2 aus zwei Teilstrings zusammensetze, kann der Compiler diese Optimierung nicht machen. Die Strings sind verschieden. Nur die equals-Methode prüft den Inhalt.
In Java musst du also immer daran denken, dass == nicht dasselbe ist wie equals. In Java
def s1 = "Groovy" def s2 = "Groo" s2 += "vy" println( s1.equals(s2)) // true println (s1 == s2) // true, da in Groovy == equals() aufruft println s1.is(s2) // false. is prüft, ob es dieselbe Referenz ist
Mit der Methode is
lässt sich wieder die Identität der Objekte prüfen. Auch hier muss ich den String s2 zusammensetzen, damit der Compiler nicht dasselbe Stringobjekt verwendet.
String in Groovy auf bestimmten Anfang prüfen
Die Methode startsWith(String prefix) prüft, ob ein String einen bestimmten Anfang hat.
def str = "GroovyString" println str.startsWith("Groovy") // true
String in Groovy auf enthaltenen Teilstring prüfen
Die Methode contains(CharSequence seq) prüft, ob der String den angegebenen Teilstring enthält
def text = "Hello Groovy World" assert text.contains("Groovy") // true assert !text.contains("Java") // false
Die Position eines Teilstrings in Groovy ermitteln
Mit indexOf und lastIndexOf lässt sich die Position eines Teilstrings ermitteln. Man kann auch vorgeben, ab wo gesucht werden soll.
def text = "Groovy Groovy" assert text.indexOf("Groovy") == 0 assert text.indexOf("Groovy", 1) == 7 // Suche ab Position 1 => das zweite Groovy passt. assert text.lastIndexOf("Groovy") == 7
Teilstrings herausgreifen
substring(int beginIndex, int endIndex)
Schneidet den String zwischen beginIndex
(inklusive) und endIndex
(exklusiv) heraus.
substring(int beginIndex)
Liefert alles ab beginIndex
bis zum Ende.
def text = "Hello Groovy" assert text.substring(0, 5) == "Hello" assert text.substring(6) == "Groovy"
Hier ist es wichtig, genau zu zählen. Das H von Hello ist die Position 0. Das o von Hello ist Position 4. Die 0 (inklusiv gerechnet) gehört zum Ergebnis. Die 5 (exklusiv gerechnet) ist die erste Position, die nicht mehr mit dazugehört.
Mehr Groovy-Wissen findest du hier.