Перейти к основному содержимому

Замыкания в Groovy

· 6 мин. чтения

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 .