Un closure es un trozo de código empaquetado como un objeto y definido entre llaves. Actúa como un método, al cual se le pueden pasar parámetros y pueden devolver valores. Es un objeto normal y corriente al cual se pasa una referencia de la misma forma que se le pasa a cualquier otro objeto.
def nombre = 'Juan'
def imprimeNombre = { println "Mi nombre es $nombre"}
imprimeNombre()
nombre = "Yolanda"
imprimeNombre()
def imprimeNombre = { nombre -> println "Mi nombre es ${nombre}"}
imprimeNombre("Juan")
imprimeNombre "Yolanda" //Los paréntesis son opcionales
//Con múltiples parámetros
def quintetoInicial = { base, escolta, alero, alapivot, pivot -> println "Quinteto inicial compuesto por: $base, $escolta, $alero, $alapivot y $pivot"}
quintetoInicial "Calderón", "Navarro", "Jiménez", "Garbajosa", "Pau Gasol"
def imprimeNombre = { println "Mi nombre es $it" }
imprimeNombre("Juan")
imprimeNombre "Yolanda"
class MetodoClosureEjemplo {
int limite
MetodoClosureEjemplo (int limite){
this.limite = limite
}
boolean validar (String valor){
return valor.length() <= limite
}
}
MetodoClosureEjemplo primero = new MetodoClosureEjemplo(8)
MetodoClosureEjemplo segundo = new MetodoClosureEjemplo(5)
Closure primerClosure = primero.&validar
def palabras = ["cadena larga", "mediana", "corta"]
assert "mediana" == palabras.find(primerClosure)
assert "corta" == palabras.find(segundo.&validar)
class MultimetodoClosureEjemplo{
int metodoSobrecargado(String cadena){
return cadena.length()
}
int metodoSobrecargado(List lista){
return lista.size()
}
int metodoSobrecargado(int x, int y){
return x * y
}
}
MultimetodoClosureEjemplo instancia = new MultimetodoClosureEjemplo()
Closure multiclosure = instancia.&metodoSobrecargado
assert 21 == multiclosure("una cadena cualquiera")
assert 4 == multiclosure(['una','lista','de','valores'])
assert 21 == multiclosure(7, 3)
def quintetoInicial = ["Calderón", "Navarro", "Jiménez", "Garbajosa", "Pau Gasol"]
salida = ''
quintetoInicial.each {
salida += it +', '
}
assert salida.take(salida.size()-2) == 'Calderón, Navarro, Jiménez, Garbajosa, Pau Gasol'
def suma = { x, y ->
x + y
}
assert 10 == suma(7,3)
assert 13 == suma.call(7,6)
def campodepruebas(repeticiones, Closure proceso){
inicio = System.currentTimeMillis()
repeticiones.times{proceso(it)}
fin = System.currentTimeMillis()
return fin - inicio
}
lento = campodepruebas(999999) { (int) it / 2 }
rapido = campodepruebas(999999) { it.intdiv(2) }
//El método lento es al menos 3 más lento que el rápido
assert rapido * 3 < lento
def suma = { x, y=3 ->
suma = x + y
}
assert 7 == suma(4,3)
assert 7 == suma(4)
def llamador (Closure closure){
closure.getParameterTypes().size()
}
assert llamador { uno -> } == 1
assert llamador { uno, dos -> } == 2
def suma = { x, y -> x + y }
def sumaUno = suma.curry(1)
assert suma(4,3) == 7
assert sumaUno(5) == 6
assert [2,4,6] == [1,2,3].collect { it * 2 } //Implícita
assert [2,4,6] == [1,2,3].collect { return it * 2 } //Explícita
assert [2,2, 6] == [1,2,3].collect {
if (it%2==1)
return it * 2
return it
}
class miClase {
public campo1, campo2, campo3, campo4 = 0
}
def miobjeto = new miClase()
miobjeto.campo1 = 2
assert miobjeto.campo1 == 2
miobjeto['campo2'] = 3
assert miobjeto.campo2 == 3
for(i=1;i<=4;i++)
miobjeto['campo'+i] = i - 1
assert miobjeto.campo1 == 0
assert miobjeto['campo2'] == 1
assert miobjeto.campo3 == 2
assert miobjeto['campo4'] == 3
class MiClase{
static main(args){
def algo = new MiClase()
algo.metodoPublicoVacio()
assert "hola" == algo.metodoNoTipado()
assert 'adios' == algo.metodoTipado()
metodoCombinado()
}
void metodoPublicoVacio(){
;
}
def metodoNoTipado(){
return 'hola'
}
String metodoTipado(){
return 'adios'
}
protected static final void metodoCombinado(){
}
}
def mapa = [a:[b:[c:1]]]
assert mapa.a.b.c == 1
//Protección con cortocircuito
if (mapa && mapa.a && mapa.a.x){
assert mapa.a.x.c == null
}
//Protección con un bloque try/catch
try{
assert mapa.a.x.c == null
} catch (NullPointerException npe){}
//Protección con el operador ?.
assert mapa?.a?.x?.c == null
class Libro{
String titulo, autor
Libro(titulo, autor){
this.titulo = titulo
this.autor = autor
}
}
//Forma tradicional
def primero = new Libro('Groovy in action', 'Dierk König')
//Mediante la palabra reservada as y una lista de parámetros
def segundo = ['Groovy in action','Dierk König'] as Libro
//Mediante una lista de parámetros
Libro tercero = ['Groovy in action','Dierk König']
assert primero.getTitulo() == 'Groovy in action'
assert segundo.getAutor() == 'Dierk König'
assert tercero.titulo == 'Groovy in action'
class Libro {
String titulo, autor
}
def primero = new Libro()
def segundo = new Libro(titulo: 'Groovy in action')
def tercero = new Libro(autor: 'Dierk König')
def cuarto = new Libro(titulo: 'Groovy in action', autor: 'Dierk König')
assert primero.getTitulo() == null
assert segundo.titulo == 'Groovy in action'
assert tercero.getAutor() == 'Dierk König'
assert cuarto.autor == 'Dierk König'
Idéntica organización que Java
package negocio
class Cliente {
String nombre, producto
Direccion direccion = new Direccion()
}
class Direccion {
String calle, ciudad, provincia, pais, codigopostal
}
import negocio.*
def clienteua = new Cliente()
clienteua.nombre = 'Universidad de Alicante'
clienteua.producto = 'Pizarras digitales'
assert clienteua.getNombre() == 'Universidad de Alicante'
import agenteexterno1.OtraClase as OtraClase1
import agenteexterno2.OtraClase as OtraClase2
def otraClase1 = new OtraClase1()
def otraClase2 = new OtraClase2()
def multimetodo(Object o) { return 'objeto' }
def multimetodo(String o) { return 'string' }
Object x = 1
Object y = 'foo'
assert 'objeto' == multimetodo(x)
assert 'string' == multimetodo(y)//En Java, esta llamada hubiera devuelto la palabra 'objeto'
public class Libro implements java.io.Serializable {
private String titulo;
public String getTitulo(){
return titulo;
}
public void setTitulo(String valor){
titulo = valor;
}
}
class Libro implements java.io.Serializable {
String titulo
}
class Persona {
String nombre, apellidos
String getNombreCompleto(){
return "$nombre $apellidos"
}
}
def juan = new Persona(nombre:"Juan")
juan.apellidos = "Martínez"
assert juan.nombreCompleto == "Juan Martínez"
class DobleValor {
def valor
void setValor(valor){
this.valor = valor
}
def getValor(){
valor * 2
}
}
def doble = new DobleValor(valor: 300)
assert 600 == doble.getValor()
assert 600 == doble.valor
assert 300 == doble.@valor
def getLista(){
return [1,2,3,4,5]
}
def suma(a, b, c, d, e){
return a + b + c + d + e
}
assert 15 == suma(*lista)
La metaprogramación consiste en escribir programas que escriben o manipulen otros programas (o a sí mismos) como datos, o que hacen en tiempo de compilación parte del trabajo que, de otra forma, se haría en tiempo de ejecución. Esto permite al programador ahorrar tiempo en la producción de código.
Clase especial que nos permite añadir métodos, constructores, propiedades y métodos estáticos utilizando una sintaxis basada en closures.
def miExpando = new Expando()
miExpando.factor = 5
miExpando.multiplica = { a -> factor * a }
assert miExpando.multiplica(4) == 20
assert miExpando.resto == null
miExpando.factor = 5
//Realmente...
miExpando.setProperty('factor',5)
miExpando.factor
//Realmente...
miExpando.getProperty('factor')
miExpando.multiplica(4)
//Realmente...
miExpando.invokeMethod('multiplica', [4] as Object[])
class MiExpando {
private dynamicProperties = [:]
void setProperty(String propName, val){
dynamicProperties[propName] = val
}
def getProperty(String propName) {
dynamicProperties[propName]
}
def methodMissing(String methodName, args){
def prop = dynamicProperties[methodName]
if (prop instanceof Closure) {
return prop(*args)
}
}
}
def miExpando = new MiExpando()
miExpando.a = 4
miExpando.b = 5
miExpando.suma = { x, y -> x + y }
assert miExpando.a == 4
assert miExpando.b == 5
assert miExpando.suma(4,5)
Todo closure tiene asociado un objeto conocido como _delegate_ que puede ser cualquier tipo de objeto y es la forma de tener un objeto que responda a determinadas llamadas a métodos y propiedades.
def miclosure = {
concat " Mundo!"
}
def s = "Hola"
miclosure.delegate = s
assert miclosure.call() == "Hola Mundo!"
¿qué pasaría si tuviéramos un método concat(String)? ¿Qué método se ejecutaría al realizar la llamada desde el closure?
def concat(String arg) {
return "Concat llamado con arg = $arg"
}
def miclosure = {
concat " Mundo!"
}
def s = "Hola"
miclosure.delegate = s
assert miclosure.call() == "Concat llamado con arg = Mundo!"
def concat(String arg) {
return "Concat llamado con arg = $arg"
}
def miclosure = {
concat " Mundo!"
}
def s = "Hola"
miclosure.delegate = s
assert miclosure.resolveStrategy == Closure.OWNER_FIRST
assert miclosure.call() == "Concat llamado con arg = Mundo!"
miclosure.resolveStrategy = Closure.OWNER_FIRST
assert miclosure.call() == "Concat llamado con arg = Mundo!"
miclosure.resolveStrategy = Closure.DELEGATE_FIRST
assert miclosure.call() == "Hola Mundo!"
miclosure.resolveStrategy = Closure.OWNER_ONLY
assert miclosure.call() == "Concat llamado con arg = Mundo!"
miclosure.resolveStrategy = Closure.DELEGATE_ONLY
assert miclosure.call() == "Hola Mundo!"
El objeto delegate por defecto de cualquier closure siempre es el propietario del closure.
def textoLargo = """La metaprogramación consiste en escribir programas que escriben o manipulan otros programas
(o a sí mismos) como datos, o que hacen en tiempo de compilación parte del trabajo que, de
otra forma, se haría en tiempo de ejecución. Esto permite al programador ahorrar tiempo en
la producción de código."""
def textoCorto = "La metaprogramación consiste en escribir programas que escriben o manipulan otros programas"
assert textoLargo instanceof java.lang.String
assert textoCorto instanceof java.lang.String
String.metaClass.cortaLosPrimeros140Caracteres = {
delegate.size() >= 140 ? "${delegate.take(137)}..." : delegate
}
println textoLargo.cortaLosPrimeros140Caracteres()
println textoCorto.cortaLosPrimeros140Caracteres()
def texto1 = "texto 1"
def texto2 = "texto 2"
texto1.metaClass.foo = {
"${delegate}foo"
}
assert texto1.foo() == "texto 1foo"
try {
assert texto2.foo() == "texto 2foo"
} catch (MissingMethodException mme) {
println "El método foo no existe"
}
También podemos sobreescribir métodos ya existentes
def texto = "En Groovy podemos sobreescribir métodos ya existentes"
def textoZeroBased = texto.substring(0)
String.metaClass.substring = { int beginIndex ->
delegate[beginIndex-1..delegate.size()-1]
}
def textoOneBased = texto.substring(1)
assert textoOneBased == textoZeroBased
String.metaClass = null
Para sobrecargar métodos estáticos
String.metaClass.'static'.valueOf = { Boolean b ->
b ? "false" : "true"
}
assert "false" == String.valueOf(true)
assert "true" == String.valueOf(false)
def builder = new groovy.xml.MarkupBuilder()
def facturas = builder.facturas {
for (dia in 1..3) {
factura(fecha: new Date() - dia) {
item(id:dia){
producto(nombre: 'Teclado', euros:876)
}
}
}
}
facturas
-
-
-
def builder = new groovy.xml.MarkupBuilder()
builder.html {
head {
title 'Facturas'
}
body {
h1 'Facturas'
for (dia in 1..3){
ul{
li (new Date() - dia).toString()
ul {
li "$dia.- Teclado => 876euros"
}
}
}
}
}
def builder = new groovy.json.JsonBuilder()
def root = builder.teachers {
professor {
firstName 'Fran'
lastName 'Garcia'
address(
city: 'Oxford',
country: 'UK',
zip: 12345,
)
married true
modules 'Groovy','Grails'
}
}
assert root instanceof Map
assert builder.toString() == '{"teachers":{"professor":{"firstName":"Fran","lastName":"Garcia","address":{"city":"Oxford","country":"UK","zip":12345},"married":true,"modules":["Groovy","Grails"]}}}'