Fechamentos em Groovy
1. Visão geral
Neste tutorial introdutório, vamos explorar o conceito de fechamentos emGroovy, um recurso-chave desta linguagem JVM dinâmica e poderosa.
Muitas outras linguagens, incluindo Javascript e Python, suportam o conceito de fechamento. No entanto, as características e o funcionamento dos fechamentos variam de idioma para idioma.
Abordaremos os principais aspectos dos fechamentos do Groovy, mostrando exemplos de como eles são usados ao longo do caminho.
2. O que é um fechamento?
Um fechamento é um bloco de código anônimo. No Groovy, é uminstance of the Closure class. Os fechamentos podem receber 0 ou mais parâmetros e sempre retornam um valor.
Além disso, um fechamento pode acessar variáveis circundantes fora de seu escopo e usá-las - junto com suas variáveis locais - durante a execução.
Além disso, podemos atribuir um fechamento a uma variável ou passá-lo como parâmetro a um método. Portanto, um fechamento fornece funcionalidade para execução atrasada.
3. Declaração de encerramento
A Groovy Closure contains parameters, the arrow →, and the code to execute. Os parâmetros são opcionais e, quando fornecidos, são separados por vírgulas.
3.1. Declaração básica
def printWelcome = {
println "Welcome to Closures!"
}
Aqui, o encerramentoprintWelcome imprime uma instrução quando invocado. Agora, vamos escrever um exemplo rápido de fechamento unário:
def print = { name ->
println name
}
Aqui, o fechamentoprint pega um parâmetro -name - e o imprime quando invocado.
Since the definition of a closure looks similar to a method, vamos compará-los:
def formatToLowerCase(name) {
return name.toLowerCase()
}
def formatToLowerCaseClosure = { name ->
return name.toLowerCase()
}
Aqui, o método e o fechamento correspondente se comportam de maneira semelhante. No entanto, existem diferenças sutis entre um fechamento e um método, que discutiremos mais tarde na seção Fechamentos vs Métodos.
3.2. Execução
Podemos executar um encerramento de duas maneiras - podemos invocá-lo como se fosse qualquer outro método ou podemos usar o métodocall.
Por exemplo, como um método regular:
print("Hello! Closure")
formatToLowerCaseClosure("Hello! Closure")
E executando com o métodocall:
print.call("Hello! Closure")
formatToLowerCaseClosure.call("Hello! Closure")
4. Parâmetros
Os parâmetros dos fechamentos do Groovy são semelhantes aos dos métodos regulares.
4.1. Parâmetro implícito
Podemos definir um fechamento unário sem um parâmetro porquewhen parameters are not defined, Groovy assumes an implicit parameter named “it”:
def greet = {
return "Hello! ${it}"
}
assert greet("Alex") == "Hello! Alex"
4.2. Parâmetros múltiplos
Aqui está um encerramento que leva dois parâmetros e retorna o resultado da multiplicação deles:
def multiply = { x, y ->
return x*y
}
assert multiply(2, 4) == 8
4.3. Tipos de Parâmetros
Nos exemplos até agora, não houve nenhum tipo fornecido com nossos parâmetros. Também podemos definir o tipo de parâmetros de fechamento. Por exemplo, vamos reescrever o métodomultiply para considerar outras operações:
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
Podemos declarar um número variável de argumentos nos fechamentos, semelhante aos métodos regulares. Por exemplo:
def addAll = { int... args ->
return args.sum()
}
assert addAll(12, 10, 14) == 36
5. Um encerramento como argumento
Podemos passarClosure como um argumento para um método Groovy regular. Isso permite que o método chame nosso fechamento para concluir sua tarefa, permitindo que personalizemos seu comportamento.
Vamos discutir um caso de uso simples: o cálculo do volume de figuras regulares.
Neste exemplo, o volume é definido como a área multiplicada pela altura. No entanto, o cálculo da área pode variar para diferentes formas.
Portanto, vamos escrever o métodovolume, que leva um fechamentoareaCalculator como argumento, e vamos passar a implementação do cálculo da área durante a invocação:
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
Vamos encontrar o volume de um cone usando o mesmo método:
assert volume({ radius -> return Math.PI*radius*radius/3 }, 5, 10) == Math.PI * 250
6. Encerramentos aninhados
Podemos declarar e invocar encerramentos dentro de um encerramento.
Por exemplo, vamos adicionar uma capacidade de registro ao já discutido fechamentocalculate:
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. Avaliação preguiçosa de cordas
GroovyStrings geralmente são avaliados e interpolados no momento da criação. Por exemplo:
def name = "Samwell"
def welcomeMsg = "Welcome! $name"
assert welcomeMsg == "Welcome! Samwell"
Mesmo se modificarmos o valor da variávelname, owelcomeMsg não vai mudar:
name = "Tarly"
assert welcomeMsg != "Welcome! Tarly"
Closure interpolation allows us to provide lazy evaluation of Strings, recalculado a partir dos valores atuais em torno deles. Por exemplo:
def fullName = "Tarly Samson"
def greetStr = "Hello! ${-> fullName}"
assert greetStr == "Hello! Tarly Samson"
Só que desta vez, mudar a variável afeta o valor da string interpolada também:
fullName = "Jon Smith"
assert greetStr == "Hello! Jon Smith"
8. Encerramentos em coleções
As coleções Groovy usam encerramentos em muitas de suas APIs. Por exemplo, vamos definir uma lista de itens e imprimi-los usando o fechamento unárioeach, que tem um parâmetro implícito:
def list = [10, 11, 12, 13, 14, true, false, "BUNTHER"]
list.each {
println it
}
assert [13, 14] == list.findAll{ it instanceof Integer && it >= 13 }
Frequentemente, com base em algum critério, podemos precisar criar uma lista a partir de um mapa. Por exemplo:
def map = [1:10, 2:30, 4:5]
assert [10, 60, 20] == map.collect{it.key * it.value}
9. Encerramentos vs Métodos
Até agora, vimos a sintaxe, a execução e os parâmetros dos encerramentos, que são bastante semelhantes aos métodos. Vamos agora comparar fechamentos com métodos.
Ao contrário de um método Groovy comum:
-
Podemos passar umClosure como um argumento para um método
-
Os fechamentos unários podem usar o parâmetroit implícito
-
Podemos atribuir umClosure a uma variável e executá-lo mais tarde, como um método ou comcall
-
Groovy determina o tipo de retorno dos fechamentos em tempo de execução
-
Podemos declarar e invocar fechamentos dentro de um fechamento
-
Os fechamentos sempre retornam um valor
Portanto, os fechamentos têm benefícios sobre os métodos regulares e são um recurso poderoso do Groovy.
10. Conclusão
Neste artigo, vimos como criar fechamentos no Groovy e exploramos como eles são usados.
Os fechamentos fornecem uma maneira eficaz de injetar funcionalidade em objetos e métodos para execução atrasada.
Como sempre, o código e os testes de unidade deste artigo estão disponíveisover on GitHub.