1. Обзор
В этом вводном руководстве мы рассмотрим концепцию замыканий в Groovy , ключевую особенность этого динамичного и мощного языка JVM.
Многие другие языки, включая Javascript и Python, поддерживают концепцию замыканий. Однако характеристики и функционирование замыканий варьируются от языка к языку.
Мы коснемся ключевых аспектов замыканий Groovy, показав примеры их использования.
2. Что такое замыкание?
Замыкание — это анонимный блок кода. В Groovy это экземпляр класса Closure
. Замыкания могут принимать 0 или более параметров и всегда возвращать значение.
Кроме того, замыкание может обращаться к окружающим переменным за пределами своей области и использовать их — вместе со своими локальными переменными — во время выполнения.
Кроме того, мы можем назначить замыкание переменной или передать ее как параметр методу. Следовательно, замыкание обеспечивает функциональность для отложенного выполнения.
3. Заявление о закрытии
Groovy Closure содержит параметры, стрелку -> и код для выполнения. Параметры являются необязательными и, если они предоставлены, разделяются запятыми.
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. Типы параметров
В примерах до сих пор не было предоставлено ни одного типа с нашими параметрами. Мы также можем установить тип параметров закрытия. Например, давайте перепишем метод умножения
, чтобы учесть другие операции:
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 String
обычно оцениваются и интерполируются во время создания. Например:
def name = "Samwell"
def welcomeMsg = "Welcome! $name"
assert welcomeMsg == "Welcome! Samwell"
Даже если мы изменим значение переменной name ,
welcomeMsg
не изменится:
name = "Tarly"
assert welcomeMsg != "Welcome! Tarly"
Интерполяция замыкания позволяет нам обеспечить ленивую оценку String
s , пересчитанную из текущих значений вокруг них. Например:
def fullName = "Tarly Samson"
def greetStr = "Hello! ${-> fullName}"
assert greetStr == "Hello! Tarly Samson"
Только на этот раз изменение переменной влияет и на значение интерполированной строки:
fullName = "Jon Smith"
assert greetStr == "Hello! Jon Smith"
8. Замыкания в коллекциях
Коллекции Groovy используют замыкания во многих своих API. Например, давайте определим список элементов и напечатаем их, используя унарное замыкание each
с неявным параметром:
def list = [10, 11, 12, 13, 14, true, false, "BUNTHER"]
list.each {
println it
}
assert [13, 14] == list.findAll{ it instanceof Integer && it >= 13 }
Часто по какому-то критерию нам может понадобиться создать список из карты. Например:
def map = [1:10, 2:30, 4:5]
assert [10, 60, 20] == map.collect{it.key * it.value}
9. Замыкания против методов
До сих пор мы видели синтаксис, выполнение и параметры замыканий, которые очень похожи на методы. Давайте теперь сравним замыкания с методами.
В отличие от обычного метода Groovy:
- Мы можем передать
замыкание
в качестве аргумента метода. - Унарные замыкания могут использовать неявный параметр
it .
- Мы можем присвоить
Closure
переменной и выполнить ее позже либо как метод, либо свызовом
- Groovy определяет возвращаемый тип замыканий во время выполнения.
- Мы можем объявлять и вызывать замыкания внутри замыкания.
- Замыкания всегда возвращают значение
Таким образом, замыкания имеют преимущества перед обычными методами и являются мощной функцией Groovy.
10. Заключение
В этой статье мы увидели, как создавать замыкания в Groovy, и рассмотрели, как они используются.
Замыкания обеспечивают эффективный способ внедрения функциональности в объекты и методы для отложенного выполнения.
Как всегда, код и модульные тесты из этой статьи доступны на GitHub .