Затваряния в Groovy

1. Общ преглед

В този уводен урок ще изследваме концепцията за затваряне в Groovy, ключова характеристика на този динамичен и мощен JVM език.

Много други езици, включително Javascript и Python, поддържат концепцията за затваряне. Характеристиките и функционирането на затварящите устройства обаче варират в зависимост от езика.

Ще засегнем ключови аспекти на затварянето на Groovy, показвайки примери за това как се използват по пътя.

2. Какво е затваряне?

Затварянето е анонимен блок код. В Groovy това е екземпляр от класа Closure . Затварянията могат да вземат 0 или повече параметри и винаги да връщат стойност.

Освен това, затварянето може да осъществи достъп до околните променливи извън обхвата и да ги използва - заедно с локалните си променливи - по време на изпълнение.

Освен това можем да присвоим затваряне на променлива или да я предадем като параметър на метод. Следователно затварянето осигурява функционалност за забавено изпълнение.

3. Декларация за закриване

Затварянето на Groovy съдържа параметри, стрелката -> и кода за изпълнение. Параметрите не са задължителни и, когато са предоставени, са разделени със запетая.

3.1. Основна декларация

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

Тук затварянето printWelcome отпечатва извлечение при извикване. Сега, нека напишем бърз пример за еднократно затваряне:

def print = { name -> println name }

Тук отпечатването на затваряне взема един параметър - име - и го отпечатва при извикване.

Тъй като определението за затваряне изглежда подобно на метод , нека ги сравним:

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

Тук методът и съответното затваряне се държат по подобен начин. Има обаче тънки разлики между затварянето и метода, които ще обсъдим по-късно в раздела Затваряния срещу методи.

3.2. Екзекуция

Можем да изпълним затваряне по два начина - можем да го извикаме като всеки друг метод или да използваме метода на повикване .

Например като обикновен метод:

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

И изпълнява с метода на повикване :

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

4. Параметри

Параметрите на затварянията Groovy са подобни на тези на обикновените методи.

4.1. Неявен параметър

Можем да дефинираме еднократно затваряне без параметър, тъй като когато параметрите не са дефинирани, Groovy приема неявен параметър, наречен „ it“ :

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

4.2. Множество параметри

Ето затваряне, което взема два параметъра и връща резултата от тяхното умножение:

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

4.3. Типове параметри

В примерите досега не е предоставен тип с нашите параметри. Можем също да зададем типа параметри на затваряне. Например, нека пренапишем метода multiply , за да разгледаме други операции:

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. Варарги

Можем да декларираме променлив брой аргументи в затварянията, подобно на обикновените методи. Например:

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

5. Закриването като аргумент

Можем да предадем Closure като аргумент на обикновен метод на Groovy. Това позволява на метода да извика нашето затваряне, за да изпълни задачата си, което ни позволява да персонализираме поведението му.

Нека обсъдим един прост случай на използване: изчисляването на обема на редовните цифри.

В този пример обемът се определя като площ, умножена по височина. Изчисляването на площта обаче може да варира за различните форми.

Затова ще напишем метода на обема , който приема аргумент за затваряне areaCalculator и ще предадем изпълнението на изчислението на площ по време на извикване:

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 

Нека намерим обем на конус, използвайки същия метод:

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

6. Загнездени затваряния

Можем да декларираме и да извикаме затваряния вътре в затваряне.

Например, нека добавим възможност за регистриране към вече обсъжданото изчисляване на затваряне:

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. Мързелива оценка на струните

Groovy Strings обикновено се оценяват и интерполират по време на създаването. Например:

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.

Затварянето осигурява ефективен начин за инжектиране на функционалност в обекти и методи за забавено изпълнение.

Както винаги, кодовете и модулните тестове от тази статия са достъпни в GitHub.