Verschlüsse in Groovy

1. Übersicht

In diesem Einführungs-Tutorial werden wir das Konzept der Schließungen in Groovy untersuchen, einem Hauptmerkmal dieser dynamischen und leistungsstarken JVM-Sprache.

Viele andere Sprachen, einschließlich Javascript und Python, unterstützen das Konzept der Schließung. Die Eigenschaften und die Funktionsweise von Verschlüssen variieren jedoch von Sprache zu Sprache.

Wir werden auf wichtige Aspekte von Groovy-Verschlüssen eingehen und Beispiele für deren Verwendung auf dem Weg zeigen.

2. Was ist eine Schließung?

Ein Abschluss ist ein anonymer Codeblock. In Groovy ist es eine Instanz der Closure- Klasse . Abschlüsse können 0 oder mehr Parameter annehmen und immer einen Wert zurückgeben.

Darüber hinaus kann ein Abschluss auf umgebende Variablen außerhalb seines Gültigkeitsbereichs zugreifen und diese - zusammen mit seinen lokalen Variablen - während der Ausführung verwenden.

Darüber hinaus können wir einer Variablen einen Abschluss zuweisen oder ihn als Parameter an eine Methode übergeben. Daher bietet ein Abschluss Funktionen für eine verzögerte Ausführung.

3. Abschlusserklärung

Ein Groovy Closure enthält Parameter, den Pfeil -> und den auszuführenden Code. Die Parameter sind optional und, sofern angegeben, durch Kommas getrennt.

3.1. Grundlegende Erklärung

def printWelcome = { println "Welcome to Closures!" }

Hier druckt der Abschluss printWelcome beim Aufrufen eine Anweisung. Lassen Sie uns nun ein kurzes Beispiel für einen unären Abschluss schreiben:

def print = { name -> println name }

Hier wird der Verschlussdruck nimmt einen Parameter - Name - und es wird gedruckt , wenn aufgerufen.

Da die Definition eines Verschlusses einer Methode ähnelt, vergleichen wir sie:

def formatToLowerCase(name) { return name.toLowerCase() } def formatToLowerCaseClosure = { name -> return name.toLowerCase() } 

Hier verhalten sich das Verfahren und der entsprechende Verschluss ähnlich. Es gibt jedoch subtile Unterschiede zwischen einem Abschluss und einer Methode, die wir später im Abschnitt "Abschlüsse vs. Methoden" erläutern werden.

3.2. Ausführung

Wir können einen Abschluss auf zwei Arten ausführen: Wir können ihn wie jede andere Methode aufrufen oder die Aufrufmethode verwenden.

Zum Beispiel als reguläre Methode:

print("Hello! Closure") formatToLowerCaseClosure("Hello! Closure") 

Und mit der Aufrufmethode ausführen :

print.call("Hello! Closure") formatToLowerCaseClosure.call("Hello! Closure")

4. Parameter

Die Parameter von Groovy-Verschlüssen ähneln denen von regulären Methoden.

4.1. Impliziter Parameter

Wir können einen unären Abschluss ohne Parameter definieren, da Groovy einen impliziten Parameter mit dem Namen " it" annimmt, wenn keine Parameter definiert sind :

def greet = { return "Hello! ${it}" } assert greet("Alex") == "Hello! Alex"

4.2. Mehrere Parameter

Hier ist ein Abschluss, der zwei Parameter verwendet und das Ergebnis ihrer Multiplikation zurückgibt:

def multiply = { x, y -> return x*y } assert multiply(2, 4) == 8

4.3. Parametertypen

In den bisherigen Beispielen wurde kein Typ mit unseren Parametern angegeben. Wir können auch die Art der Verschlussparameter einstellen. Schreiben wir zum Beispiel die Multiplikationsmethode neu , um andere Operationen zu berücksichtigen:

def calculate = {int x, int y, String operation -> def result = 0 switch(operation) { case "ADD": result = x+y break case "SUB": result = x-y break case "MUL": result = x*y break case "DIV": result = x/y break } return result } assert calculate(12, 4, "ADD") == 16 assert calculate(43, 8, "DIV") == 5.375

4.4. Varargs

Wir können eine variable Anzahl von Argumenten in Abschlüssen deklarieren, ähnlich wie bei regulären Methoden. Zum Beispiel:

def addAll = { int... args -> return args.sum() } assert addAll(12, 10, 14) == 36

5. Ein Abschluss als Argument

Wir können einen Closure als Argument an eine reguläre Groovy-Methode übergeben. Auf diese Weise kann die Methode unseren Abschluss aufrufen, um seine Aufgabe abzuschließen, und ihr Verhalten anpassen.

Lassen Sie uns einen einfachen Anwendungsfall diskutieren: die Berechnung des Volumens regulärer Zahlen.

In diesem Beispiel wird das Volumen als Fläche multipliziert mit der Höhe definiert. Die Flächenberechnung kann jedoch für verschiedene Formen variieren.

Daher schreiben wir die Volume- Methode, die einen Closure AreaCalculator als Argument verwendet, und übergeben die Implementierung der Bereichsberechnung während des Aufrufs:

def volume(Closure areaCalculator, int... dimensions) { if(dimensions.size() == 3) { return areaCalculator(dimensions[0], dimensions[1]) * dimensions[2] } else if(dimensions.size() == 2) { return areaCalculator(dimensions[0]) * dimensions[1] } else if(dimensions.size() == 1) { return areaCalculator(dimensions[0]) * dimensions[0] } } assert volume({ l, b -> return l*b }, 12, 6, 10) == 720 

Lassen Sie uns ein Volumen eines Kegels mit der gleichen Methode finden:

assert volume({ radius -> return Math.PI*radius*radius/3 }, 5, 10) == Math.PI * 250

6. Verschachtelte Verschlüsse

Wir können Schließungen innerhalb einer Schließung deklarieren und aufrufen.

Fügen wir zum Beispiel dem bereits diskutierten Berechnungsabschluss eine Protokollierungsfunktion hinzu :

def calculate = {int x, int y, String operation -> def log = { println "Performing $it" } def result = 0 switch(operation) { case "ADD": log("Addition") result = x+y break case "SUB": log("Subtraction") result = x-y break case "MUL": log("Multiplication") result = x*y break case "DIV": log("Division") result = x/y break } return result }

7. Lazy Evaluation von Strings

Groovy String s werden normalerweise zum Zeitpunkt der Erstellung ausgewertet und interpoliert. Zum Beispiel:

def name = "Samwell" def welcomeMsg = "Welcome! $name" assert welcomeMsg == "Welcome! Samwell"

Even if we modify the value of the name variable, the welcomeMsg is not going to change:

name = "Tarly" assert welcomeMsg != "Welcome! Tarly"

Closure interpolation allows us to provide lazy evaluation of Strings, recalculated from the current values around them. For example:

def fullName = "Tarly Samson" def greetStr = "Hello! ${-> fullName}" assert greetStr == "Hello! Tarly Samson"

Only this time, changing the variable affects the interpolated string's value as well:

fullName = "Jon Smith" assert greetStr == "Hello! Jon Smith"

8. Closures in Collections

Groovy Collections use closures in many of their APIs. For example, let's define a list of items and print them using the unary closure each, which has an implicit parameter:

def list = [10, 11, 12, 13, 14, true, false, "BUNTHER"] list.each { println it } assert [13, 14] == list.findAll{ it instanceof Integer && it >= 13 }

Often, based on some criterion, we may need to create a list from a map. For instance:

def map = [1:10, 2:30, 4:5] assert [10, 60, 20] == map.collect{it.key * it.value} 

9. Closures vs Methods

So far, we've seen the syntax, execution, and parameters of closures, which are fairly similar to methods. Let's now compare closures with methods.

Unlike a regular Groovy method:

  • We can pass a Closure as an argument to a method
  • Unary closures can use the implicit it parameter
  • We can assign a Closure to a variable and execute it later, either as a method or with call
  • Groovy determines the return type of the closures at runtime
  • We can declare and invoke closures inside a closure
  • Closures always return a value

Hence, closures have benefits over regular methods and are a powerful feature of Groovy.

10. Conclusion

In this article, we’ve seen how to create closures in Groovy and explored how they are used.

Abschlüsse bieten eine effektive Möglichkeit, Objekte und Methoden für eine verzögerte Ausführung mit Funktionen zu versehen.

Wie immer sind die Code- und Unit-Tests aus diesem Artikel auf GitHub verfügbar.