1. Introducción a Groovy
En esta primera sesión del módulo, empezaremos explicando qué es el lenguaje de programación Groovy y, cómo, dónde y porqué se creo para ponernos un poco en situación. Posteriormente, pasaremos a implementar nuestro primer ejemplo en Groovy, el típico Hola Mundo y lo compararemos con lo que sería el mismo ejemplo en Java.
Seguidamente describiremos detalladamente algunos aspectos básicos de Groovy, tales como la sintaxis, los tipos de datos y el trabajo con colecciones.
1.1. ¿Qué es Groovy?
Groovy nació con la intención de ser un lenguaje rico en características típicas de lenguajes dinámicos, así como para interactuar sin problemas en el entorno Java, para de esta forma, utilizar los beneficios de estos lenguajes dinámicos en una plataforma tan robusta como es Java y todo su entorno. Posiblemente, habrá lenguajes que tengan más características típicas de los lenguajes dinámicos que Groovy o que se integren mejor en la Plataforma Java, pero ninguno de estos, presenta una combinación de estos detalles (lenguaje dinámico y entorno Java) tan eficiente como Groovy. Pero, ¿cómo podríamos definir exactamente Groovy?
Una buena definición de Groovy podría ser la que se nos proporciona desde la propia página web de Groovy (http://groovy.codehaus.org):
Definición Groovy es un lenguaje de programación ágil y dinámico diseñado para la Plataforma Java con determinadas características inspiradas en lenguajes como Python, Ruby o Smalltalk, poniéndolas a disposición de los programadores Java mediante una sintaxis típica de Java. Groovy se integra sin ningún problema con cualquier librería Java y se compila directamente a código byte, con lo que Java y Groovy pueden convivir sin ningún tipo de problemas. |
En ocasiones se tacha a Groovy de ser un lenguaje meramente de script, pero esto es un error, a pesar de que Groovy funciona realmente bien como lenguaje script. Groovy puede ser precompilado a Java bytecode, estar integrado en aplicaciones Java, construir robustas aplicaciones web e incluso ser la base de una aplicación completa por si solo.
Se puede afirmar rotundamente que Groovy está destinado a ser utilizado en la Plataforma Java, no en vano muchas partes de Groovy están desarrolladas en Java, así que cuando decimos que al programar en Groovy, estamos realmente programando en Java, no estamos diciendo ninguna barbaridad. Todas las características de Java están esperando a ser utilizadas en Groovy, incluido todo el conjunto de librerías de Java.
1.1.1. Integración con Java
Cuando hablamos de integración con Java, nos referimos a dos aspectos. Por un lado, de integración imperceptible, puesto que Groovy es simplemente una nueva forma de crear nuestras habituales clases Java. Al fin y al cabo, Groovy es Java con un archivo jar como dependencia. Por otro lado, la sintaxis de Groovy es más precisa y compacta que en Java y a pesar de eso, cualquier programador Java puede entender el funcionamiento de un fragmento de código escrito en Groovy.
1.1.2. ¿A quién va dirigido Groovy?
A los programadores Java
Un programador Java con muchos años de experiencia conocerá todas las partes importantes del entorno Java y su API, así como otros paquetes adicionales. Sin embargo, algo aparentemente tan sencillo como listar todos los archivos recursivamente que cuelgan de un directorio se hace demasiado pesado en Java y suponiendo que existiera la función eachFileRecurse() tendríamos algo similar a:
public class ListFiles {
public static void main(String[] args){
new java.io.File(".").eachFileRecurse(
new FileListener() {
public void onFile (File file) {
System.out.println(file.toString());
}
}
);
}
}
Mientras que con Groovy podría quedar en una sola línea que podríamos ejecutar incluso en línea de comandos de la siguiente forma:
groovy -e "new File('.').eachFileRecurse { println it}"
A los programadores de scripts
Groovy también va dirigido a aquellos programadores que han pasado muchos años trabajando con lenguajes de programación como Perl, Ruby, Python o PHP. En ocasiones, sobre todo en las grandes empresas, los clientes requieren obligatoriamente el uso de una Plataforma robusta como puede ser Java. Estos programadores deben reciclarse, ya que no es factible cualquier solución que pase por utilizar un lenguaje script como los mencionados anteriormente.
Continuamente este tipo de programadores afirman que se sienten frustrados por toda la verborrea que conlleva un lenguaje como Java y la frase "con un lenguaje como Ruby, podría escribir todo este método en una sola línea" es su pan de cada día. Groovy puede darles lo que ellos piden y supone una vía de escape hacía adelante a este tipo de programadores.
A los programadores ágiles y extremos
Este tipo de programadores están acostumbrados a trabajar con la próxima revisión tan cercana, que pararse a pensar en utilizar un nuevo lenguaje de programación queda fuera de sus principales objetivos. Sin embargo, una de los aspectos más importantes para los programadores ágiles, consiste en tener siempre a su disposición los mejores recursos y metodologías para llevar a cabo su trabajo.
Y ahí es donde Groovy les puede aportar mucho, ya que Groovy es un lenguaje perfecto para realizar esas tareas de automatización tan habituales en sus proyectos. Estas tareas van desde la integración continua o la puesta en práctica de teorías como TDD (Test Driven Development).
1.2. Instalación
Antes de avanzar en la sesión, vamos a ver como se realizaría la instalación de Groovy en nuestro ordenador. Aunque podríamos descargarnos la última versión de Groovy desde la web oficial http://groovy.codehaus.org/Download, nosotros vamos a utilizar un gestor de paquetes conocido como sdkman (http://sdkman.io) (antes conocido como gvmtool que nos permitirá gestionar varias versiones de Groovy en una misma máquina de forma muy sencilla. Además, esta misma herramienta nos servirá para instalar y gestionar Grails, entre otras interesantes librerías como Gradle o Vert.x.
Para instalar sdkman y groovy simplemente debemos en línea de comandos:
curl -s get.sdkman.io | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk list groovy
sdk install groovy 2.4.5
Si necesitaramos alternar entre varias versiones de Groovy, simplemente deberías ejecutar:
sdk use groovy 1.8.0
1.3. Hola Mundo
Ahora que ya tenemos una primera idea de lo que es Groovy, vamos a pasar a desarrollar nuestro primer ejemplo y a realizar nuestras primeras pruebas de ejecución y compilación de archivos Groovy. Pero antes de eso, vamos a introducir las diferentes formas de ejecutar nuestros programas en Groovy.
Tras la instalación de Groovy con la herramienta sdk, vamos a disponer de tres comandos que son groovysh, groovyConsole y groovy, entre otros. Podemos utilizar cualquiera de estos tres archivos para empezar a realizar nuestras pruebas con código Groovy. Vamos a verlos uno a uno:
1.3.1. groovysh
Permite ejecutar en línea de comandos código Groovy de forma interactiva. Más información en http://groovy.codehaus.org/Groovy+Shell.
groovy:000> "Hola Mundo!"
==== > Hola Mundo!
Otro ejemplo podría ser:
groovy:000> saludo = "Hola"
===> Hola
groovy:000> "${saludo} Mundo!"
===> Hola Mundo!
E incluso podemos definir funciones con parámetros:
groovy:000> def hola(nombre){
groovy:001> println "Hola $nombre"
groovy:002> }
===> true
groovy:000> hola("Fran")
Hola Fran
==== > null
1.3.2. groovyConsole
Mediante una interfaz Swing, groovyConsole actua como intérprete de comandos Groovy. Tiene la posibilidad de cargar y salvar ficheros desde su menú File. Más información en http://groovy.codehaus.org/Groovy+Console

Como podemos observar, en la parte superior podemos escribir nuestro código Groovy, mientras que la parte inferior nos muestra la salida del programa ejecutado.
Además la groovyConsole nos permite también indicarle archivos jar externos que serán importados automáticamente en el código de nuestros scripts.
1.3.3. groovy
El último método sirve para ejecutar archivos groovy y simplemente ejecutando en la línea de comandos groovy holamundo.groovy, podemos ver la salida producida por el programa. En la siguiente imagen, puedes comprobar el funcionamiento de este método con un fichero de ejemplo que realiza el cálculo de la sucesión de Fibonacci.

Quizás ya te hayas dado cuenta, pero a diferencia de en Java, Groovy puede ejecutar sus archivos sin necesidad de compilarlos previamente. Esta forma de utilizar Groovy se llama Modo directo y nuestro código es ejecutado sin necesidad de generar archivos ejecutables. Se puede decir que nuestros archivos son interpretados. Sin embargo, existe otra forma de ejecutar nuestros programas Groovy y se llama Modo precompilado. En el modo precompilado, en primer lugar se compila el programa groovy y posteriormente se ejecuta. El programa compilado es compatible con los archivos bytecode de Java.
Para compilar nuestros programas Groovy, utilizamos una sentencia en línea de comandos llamada groovyc (también disponible tras la instalación de Groovy). Si quisieramos compilar el anterior ejemplo (fibonacci.groovy), ejecutaríamos groovyc -d classes fibonacci.groovy. Con el parámetro -d le hemos indicado que nos genere los archivos compilados en el directorio classes, y en el caso de que no existiera, lo crearía. Si observamos el contenido de este directorio, veremos que hay un par de ficheros .class. El número de archivos dependerá del propio script, de la misma forma que sucede cuando compilamos archivo en Java.
Una vez ya tenemos nuestro programa compilado, vamos a ver como podríamos ejecutarlo con Java. !Sí, con Java!, ya que para ejecutar un programa en Groovy debemos hacer exactamente lo mismo que hacemos en Java. Simplemente, debemos añadir al classpath el archivo jar de Groovy. Debemos hacer lo mismo con el directorio donde residen nuestros archivos compilados. Quedaría algo así:
java -cp $GROOVY_HOME/embeddable/groovy-all-2.4.0.jar:classes fibonacci
1.4. Características
En la siguiente sección, vamos a introducir diferentes aspectos básicos del lenguaje Groovy, tales como la introducción de comentarios, diferencias y similitudes con Java, la brevedad del código en Groovy y otros pequeños detalles del lenguaje.
1.4.1. Comentarios
Los comentarios en Groovy siguen el mismo formato que en Java. Esto es:
-
//, comentarios de una línea
-
/* …. */, comentarios multilínea
-
/** …. */, comentarios del estilo Javadoc, que nos permitirá documentar nuestros programas con Groovydoc
Sin embargo, también podemos introducir en la primera línea comentarios del estilo shebang con la combinación de caracteres #!
1.4.2. Comparando la sintaxis de Java y Groovy
Es habitual leer que la sintaxis de Groovy es una superclase de Java y esto es debido a las grandes similitudes que hay entre ambos lenguajes. Sin embargo, no es correcto afirmar que un lenguaje es una superclase del otro. Existen pequeños detalles de programación en Java que no existen en Groovy, como puede ser que el operador == que en Groovy tiene unas connotaciones diferentes.
Pero aparte de algunas pequeñas diferencias, podemos decir que gran parte de la sintaxis de Java es válida en Groovy. Estas similitudes son:
-
Mecanismo de paquetes
-
Sentencias
-
Definición de clases y métodos
-
Estructuras de control
-
Operadores, asignaciones y expresiones
-
Manejo de excepciones
-
Declaración de literales
-
Instanciación de objetos y llamadas a métodos
No obstante, Groovy posee un valor añadido gracias a:
-
Fácil acceso a los objetos Java a través de nuevas expresiones y operadores
-
Nuevas formas de declarar objetos
-
Nuevas estructuras de control
-
Nuevos tipos de datos con sus correspondientes operadores y expresiones
-
Todo se gestiona como un objeto
Estas nuevas características hacen que Groovy sea un lenguaje fácil de leer y de entender.
1.4.3. Brevedad del lenguaje
Groovy pretende evitar en todo momento toda la ceremonia que acompaña a Java en sus programas y nos permite obviar determinados aspectos obligatorios y centrarnos en nuestro objetivo. Para empezar, se elimina la obligatoriedad de utilizar los puntos y comas (;) al finalizar cada línea. Si lo pensamos un poco es lógico, porque ¿cuántos de nosotros escribimos más de una sentencia en la misma línea del código fuente?. Esto no quiere decir que en Groovy no podamos escribir puntos y coma al final de cada línea, simplemente no es obligatorio.
Además, también se elimina la obligatoriedad de utilizar paréntesis al pasar parámetros en la llamada a funciones.
Otro ejemplo de la brevedad del lenguaje que aporta Groovy es el siguiente ejemplo. Mientras que en Java tendríamos lo siguiente:
java.net.URLEncoder.encode ("a b");
en Groovy tendríamos
URLEncoder.encode 'a b'
Esto no sólo significa que el código en Groovy va a ser más corto, sino también más expresivo evitando toda la parafernalia que acompaña a Java en determinadas ocasiones.
Groovy además importa automáticamente los paquetes groovy.lang.*, groovy.util.*, java.lang.*, java.util.*, java.net.* y java.io.*, así como las clases java.Math.BigInteger y java.Math.BigDecimal, así que siempre vas a poder utilizar todas sus clases sin necesidad de que sean importados sus paquetes al inicio del programa. Esto también es diferente a Java, donde sólo se importa automáticamente el paquete java.lang.*.
Resumiendo. Groovy nos permite olvidarnos de los paréntesis, la declaración de paquetes, puntos y comas y centrarnos en la funcionalidad de nuestros programas y métodos.
1.4.4. Aserciones
Las aserciones permiten desde Java 1.4 comprobar si nuestro programa funciona como debería funcionar y Groovy no iba a ser menos, así que también las incluye. Las aserciones se insertan en medio de nuestro código y se utilizan, por ejemplo, para comprobar posibles incongruencias en las variables pasadas a un método. Fundamentalmente las aserciones se utilizarán en los tests que desarrollemos para nuestra aplicación y que asegurarán que las cosas funcionan correctamente. Algunos ejemplos de aserciones serían:
assert(true)
assert 1 == 1
def x = 1
assert x == 1
def y = 1; assert y == 1
Como se puede comprobar en los ejemplos las aserciones pueden tomar expresiones completas y no únicamente variables, como en el ejemplo assert 1 == 1. Además las aserciones no sólo nos servirán para comprobar el estado de nuestro programa, sino que también podremos reemplazar determinados comentarios por aserciones, que además de clarificar el código fuente de nuestro programa, nos sirve también para pasar pequeñas pruebas a nuestro código.
Cuando usamos aserciones, es una buena práctica incluir un mensaje que nos clarifique el error que se ha producido. Esto lo podemos hacer añadiendo después de la aserción el mensaje de texto que queremos precedido de :, como en el siguiente ejemplo:
assert 1==2 : "Desde cuando 1 es igual a 2"
//Obteniendo el siguiente mensaje
//Exception thrown: Desde cuando 1 es igual a 2. Expression: (1 == 2)
Además, en las últimas versiones de Groovy se ha mejorado el sistema de mensajes mostrados cuando fallan las aserciones fallan. Si por ejemplo, ejecutamos el siguiente fragmento de código:
assert new File('foo.bar') == new File('example.txt')
Groovy nos mostraría el siguiente mensaje de error
Exception thrown
Assertion failed:
assert new File('foo.bar') == new File('example.txt')
| | |
foo.bar | example.txt
false
1.4.5. Un primer vistazo al código en Groovy
Antes de comenzar a ver las principales características de Groovy, podéis encontrar toda la documentación de sus clases y librerías en la dirección http://groovy.codehaus.org/gapi.
Declaración de clases
Las clases son la clave de cualquier lenguaje orientado a objetos y Groovy no iba a ser menos. El siguiente fragmento consiste en la declaración de la clase Libro, con una sola propiedad Titulo, el constructor de la clase y un método getter para obtener el título del libro.
class Libro {
private String titulo
Libro (String elTitulo){
titulo = elTitulo
}
String getTitulo(){
return titulo
}
}
Se puede comprobar como el código es bastante similar a Java. Sin embargo, no hay la necesidad de emplear modificadores de acceso a los métodos, ya que por defecto siempre serán públicos.
Scripts en Groovy
Los scripts en Groovy son archivos de texto plano con extensión .groovy y que pueden ser ejecutados, tal y como comentábamos en una sección anterior mediante el comando groovy miarchivo.groovy. A diferencia de en Java, en Groovy no necesitamos compilar nuestros scripts, ya que son precompilados por nosotros al mismo tiempo que lo ejecutamos, así que a simple vista, es como si pudiéramos ejecutar un archivo groovy.
En Groovy podemos tener sentencias fuera de la definición de las clases e incluso podemos añadir métodos a nuestros scripts que también queden fuera de las definiciones de nuestras clases, si con ello conseguimos clarificar nuestro código, con lo que el método main() típico de Java, no es necesario en Groovy y sólo lo necesitaremos en el caso de que nuestro script necesite parámetros pasados en línea de comandos. Veamos un ejemplo:
Libro cgg = new Libro('Curso GroovyGrails')
assert cgg.getTitulo() == 'Curso GroovyGrails'
assert getTituloAlReves(cgg) == 'sliarGyvoorG osruC'
String getTituloAlReves(libro) {
titulo = libro.getTitulo()
return titulo.reverse()
}
Podemos comprobar que llamamos al método getTituloAlReves() incluso antes de que éste sea declarado. Otro aspecto importante es que no necesitamos compilar nuestra clase Libro, ya que Groovy lo hace por nosotros. El único requisito es que la clase Libro se encuentre en el CLASSPATH.
GroovyBeans
Fundamentalmente los JavaBeans son clases que definen propiedades. Por ejemplo, en nuestro ejemplo de la clase Libro, sus propiedades serían el título, el autor, la editorial, etc. Estos JavaBeans tienen métodos para obtener los valores de estas propiedades (getters) y modificarlas (setters). Estos métodos deben ser definidos en cada JavaBean, con lo que esta tarea se vuelve algo repetitiva.
class Libro{
String titulo;
String getTitulo(){
return this.titulo
}
void setTitulo(String str){
this.titulo = new String(str)
}
}
Sin embargo, Groovy gestiona esta definición de los métodos get() y set() por nosotros, con lo que ya no debemos preocuparnos. Con esto, el código anterior, en Groovy quedaría así:
class Libro{
String titulo
}
Además, tenemos la gran ventaja de que podemos sobreescribir estos métodos en el caso de que queramos modificar su comportamiento básico.
En realidad, lo que hace Groovy es tratar de entender el comportamiento de las propiedades de un bean a partir de como están definidas. Con esto tenemos los siguientes casos:
-
Si una propiedad de una clase es declarada sin ningún modificador, se genera un campo privado con sus correspondientes métodos públicos getter() y setter()
-
Si una propiedad de una clase es declarada como final, dicha propiedad será privada y se definirá también su getter(), pero no su setter()
-
Si necesitamos una propiedad de tipo privado o protegida debemos declarar sus métodos getter() y setter() de forma privada o protegida
Todo son objetos
En Groovy, a diferencia que en Java, los números son tratados como objetos y no como tipos primitivos. En Java no podemos utilizar métodos de la clase Integer si el entero es de tipo primitivo y tampoco podemos hacer y*2 si la variable y es un objeto. Esto también es diferente en Groovy, ya que podemos utilizar operadores y métodos indiferentemente.
def x = 1
def y = 2
assert x + y == 3
assert x.plus(y) == 3
assert x instanceof Integer
En el ejemplo anterior se puede observar que hemos podido utilizar el operador + de los tipos primitivos y el método plus() de la clase Integer.
En realidad, en Groovy, todo es un objeto, con lo que aunque declaremos variables como tipos de datos primitivos en realidad Groovy los convierte a tipo de dato referencia, tal y como puedes ver en los siguientes ejemplos:
double d = 10.23
assert d instanceof Double
char a = 'a'
assert a instanceof Character
Operador ==
Es importante comentar también que en Groovy el operador == tiene una connotación diferente a la que tiene en Java. En Java, este operador significa igualdad en variables primitivas mientras que en objetos significa objetos idénticos. En Groovy, el significado de este operador es el mismo que la función equals(). Si necesitamos comprobar la identidad de dos variables siempre podremos utilizar el método is() tal y como vemos en el siguiente ejemplo:
if (foo.is(bar))
println "foo y bar son el mismo objeto"
La verdad en Groovy
Cabe aquí comentar lo que se conoce como la verdad en Groovy que es un término que se refiere a que se considera como cierto en Groovy y que no. Veamos algunos ejemplos:
def a = true
def b = true
def c = false
assert a
assert a && b
assert a || c
assert !c
Hasta aquí ninguna novedad, pero ¿qué pasa si no estamos evaluando directamente un booleano?. Pues que Groovy añade una serie de reglas para indicar si esa expresión es cierta o no. Por ejemplo, en las colecciones (listas y mapas) se entenderá que una variable de estos tipos será cierta cuando contenga al menos un elemento.
def numeros = [1,2,3]
assert numeros //cierto, ya que numeros no está vacía
numeros = [] //vacíamos la lista
assert !numeros //cierto, ya que ahora la lista está vacía
assert ['one':1] //este mapa contiene un elemento con lo que se evalúa a cierto
assert ![:] //este mapa está vacío
En lo que a las cadenas de texto se refiere, siempre que éstas no sean la cadena vacía, se evaluarán a cierto.
// Strings
assert 'Esto es cierto'
assert !''
//GStrings
def s = ''
assert !("$s")
s = 'x'
assert ("$s")
Si evalúamos un número, siempre que éste no sea cero se evaluará a cierto.
assert !0
assert 1
Por último, siempre que un objeto no sea null éste se evaluará a cierto.
assert new Object()
assert !null
Estructuras de control en Groovy
Algunas estructuras de control en Groovy son tratadas de la misma forma que en Java. Estos bloques son if-else, while, switch y try-catch-finally, aunque es conveniente hacer algunas anotaciones. En los condicionales, null es tratado como false, mientras que !null será true.
Con respecto a la sentencia if, Groovy soporta la sentencia de forma anidada.
if ( ... ) {
...
} else if (...) {
...
} else {
...
}
De igual forma, se introduce también el operador ternario.
def y = 5
def x = (y > 1) ? "funciona" : "falla"
assert x == "funciona"
Así como el operador Elvis
def usuario = [nombre:"Fran"]
def nombre = usuario.nombre ?: "Anónimo"
assert nombre == "Fran"
usuario = [:]
nombre = usuario.nombre ?: "Anónimo"
assert nombre == "Anónimo"
Con respecto a la estructura switch, la única diferencia entre Groovy y Java es que en la condición se puede utilizar cualquier tipo de comprobación, tal y como vemos en el siguiente ejemplo:
def x = 1.23
def result = ""
switch ( x ) {
case "foo":
result = "found foo"
// lets fall through
case "bar":
result += "bar"
case [4, 5, 6, 'inList']:
result = "list"
break
case 12..30:
result = "range"
break
case Integer:
result = "integer"
break
case Number:
result = "number"
break
default:
result = "default"
}
assert result == "number"
Groovy permite realizar las siguientes comprobaciones en una estructura switch:
-
Comparar la clase de una variable
-
Expresiones regulares
-
Comprobar si la variable está contenida en una colección
-
Y en último lugar, se comprueba la igualdad entre la variable evaluada y el caso en particular
Los bloques de código del tipo for utilizan la notación for(i in x){cuerpo} donde x puede ser cualquier cosa que Groovy sepa como iterar (iteradores, enumeraciones, colecciones, mapas, rangos, etc.)
for(i in 1..10)
println i
for(i in [1,2,3,4,5,6,7,8,9,10])
println i
En Groovy es una práctica habitual utilizar un closure para simular un bucle for como por ejemplo en el siguiente código
def alumnos = ['Pedro','Miguel','Alejandro','Elena']
alumnos.each{nombre -> println nombre}
1.5. Tipos de datos en Groovy
1.5.1. Tipos primitivos y referencias
En Java, existen los tipos de datos primitivos (int, double, float, char, etc) y las referencias (Object, String, etc), Los tipos de datos primitivos son aquellos que tienen valor por si mismos, bien sea un entero, un carácter o un número en coma flotante y es imposible crear nuevos tipos de datos primitivos.
Mientras que las referencias son identificadores de instancias de clases Java y, como su nombre indica, simplemente es una referencia a un objeto. En los tipos de datos primitivos es imposible realizar llamadas a métodos y éstos no pueden ser utilizados en aquellos lugares donde se espera la presencia de un tipo java.lang.Object. Esto hace que determinados fragmentos de código de nuestros programas, se compliquen demasiado, como puede ser el siguiente ejemplo que realiza la suma posición por posición de un par de vectores de enteros.
ArrayList resultados = new ArrayList();
for (int i=0; i < listaUno.size(); i++){
Integer primero = (Integer)listaUno.get(i);
Integer segundo = (Integer)listaDos.get(i);
int suma = primero.intValue() + segundo.intValue();
resultados.add(new Integer(suma));
}
En Groovy, todo es un objeto y una solución al ejemplo anterior podría ser utilizando el método plus() que Groovy añade al tipo Integer: resultados.add(primero.plus(segundo)), con lo que nos podríamos ahorrar el paso de la conversión de tipo de dato referencia a tipo de dato primitivo (primero.intValue()).
Sin embargo, esta solución también se podría conseguir en Java si se añadiera el método plus a la clase Integer. Así que Groovy decide ir un poco más lejos y permite la utilización de operadores entre objetos, con lo que la solución en Groovy sería resultados.add (primero + segundo).
De esta forma, lo que en Groovy puede parecer una variable de tipo de dato primitivo, en realidad es una referencia a una clase Java, tal y como se muestra en la siguiente tabla.
Tipo primitivo | Tipo referencia |
---|---|
byte |
java.lang.Byte |
short |
java.lang.Short |
int |
java.lang.Integer |
long |
java.lang.Long |
float |
java.lang.Float |
double |
java.lang.Double |
char |
java.lang.Character |
boolean |
java.lang.Boolean |
Así que cada vez que utilices un tipo de datos primitivo en tus programas en Groovy, en realidad estás utilizando la correspondiente clase indicada en la tabla anterior, con lo que, puedes ahorrarte el uso de tipos primitivos en Groovy, porque por detrás se está haciendo una conversión a un tipo de dato referencia.
1.5.2. Boxing, unboxing y autoboxing
La conversión de un tipo de dato primitivo a un tipo de dato referencia se conoce en Java como boxing, mientras que la conversión de un tipo de dato referencia a un tipo de dato primitivo se conoce unboxing. Groovy automatiza estas operaciones en lo que se conoce como autoboxing.
Pero, si Groovy convierte todo a un tipo de dato referencia, ¿qué pasa con aquellos métodos de Java que esperan un parámetro de tipo de dato primitivo? No hay de que preocuparse, el autoboxing de Groovy se encarga de eso. Por ejemplo, en el método indexOf de la clase java.lang.String se espera como parámetro un entero (int) que indica el carácter buscado en la cadena, devolviendo también un entero indicando la posición en la que se ha encontrado el caracter. Si probamos el siguiente ejemplo, veremos como todo funciona correctamente, ya que Groovy se encarga de realizar el autoboxing allí donde considere oportuno, en este caso, en el paso del parámetro a la función indexOf. El siguiente código trata de obtener la posición de la primera 'o' de la cadena.
assert 'Hola Mundo'.indexOf(111) == 1
En un principio Groovy, debería convertir el tipo de datos int del valor 111 a Integer, sin embargo, la función indexOf() requiere un parámetro de tipo int, con lo que el autoboxing de Groovy funciona de tal forma para convertir el parámetro al tipo de dato requerido, en este caso, int.
Otro aspecto interesante del autoboxing de Groovy es que no siempre se ejecuta el autoboxing para la realización de determinadas operaciones. Por ejemplo, en la operación 1 + 2, podemos pensar que los valores 1 y 2 son del tipo referencia Integer, lo cual es cierto y que para poder realizarse la operación, éstos deben ser convertidos a tipo int, lo cual no es cierto.
De Groovy se dice que es incluso más orientado a objetos que Java y se dice por cuestiones como esta. En la operación 1 + 2, lo que Groovy está realmente ejecutando es 1.plus(2), con lo que no es necesaria ninguna conversión para realizar esta operación.
1.5.3. Tipado dinámico
Hasta el momento, en prácticamente todos los ejemplos que hemos visto en Groovy, hemos obviado especificar los tipos de datos utilizados, dejando que Groovy lo haga por nosotros. Esto es lo que se conoce como tipado dinámico y en este punto, vamos a ver los pros y los contras de su uso. La siguiente tabla, muestra un ejemplo con definiciones de variables y como actúa Groovy en cada caso.
Sentencia | Tipo de variable |
---|---|
def a = 2 |
java.lang.Integer |
def b = 0.4f |
java.lang.Float |
def c = 'a' |
java.lang.String |
int d = 3 |
java.lang.Integer |
float e = 4 |
java.lang.Float |
char f = '1' |
java.lang.Character |
Integer g = 6 |
java.lang.Integer |
String h = '1' |
java.lang.String |
Character i = 'a' |
java.lang.Character |
def a = 2
def b = 0.4f
def c = 'a'
int d = 3
float e = 4
char f = '1'
Integer g = 6
String h = '1'
Character i = 'a'
assert a instanceof java.lang.Integer
assert b instanceof java.lang.Float
assert c instanceof java.lang.String
assert d instanceof java.lang.Integer
assert e instanceof java.lang.Float
assert f instanceof java.lang.Character
assert g instanceof java.lang.Integer
assert h instanceof java.lang.String
assert i instanceof java.lang.Character
La palabra reservada def se utiliza cuando no queremos especificar ningún tipo de dato en especial y dejamos que Groovy decida por nosotros, tal y como aparece en los tres primeros ejemplos. En los tres ejemplos siguientes, podemos ver como independientemente de declarar una variable como tipo de dato primitivo, ésta acabará siendo un tipo de dato referencia. Los tres últimos ejemplos, servirán a la persona que esté leyendo el código para entender que esa variable es un objeto.
Aquí es importante resaltar que Groovy es un lenguaje de tipado dinámico de datos seguro, lo que quiere decir, que Groovy no nos va a permitir realizar operaciones de una determinada clase a un objeto definido de forma diferente. Por ejemplo, en el trozo de código String f = '1', la variable f nunca va a poder ser utilizada para realizar operaciones matemáticas como si fuera de la clase java.lang.Number salvo que hagamos la correspondiente conversión.
Poder elegir si utilizamos tipado dinámico o estático, es una de las mejores cosas que tiene Groovy. En Internet existen muchos foros de discusión creados a partir de este debate donde se exponen los pros y los contras de cada método. El tipado estático nos proporciona más información para la optimización de nuestros programas y revelan información adicional sobre el significado de la variable o del parámetro utilizado en un método determinado.
Por otro lado, el tipado dinámico no sólo es el método utilizado por los programadores vagos que no quieren estar definiendo los tipos de las variables, sino que también se utiliza cuando la salida de un método es utilizado como entrada de otro sin tener que hacer ningún trabajo extra por nuestra parte. De esta forma, el programador deja a Groovy que se encargue de la conversión de los datos en caso de que sea necesario y factible.
Otro aspecto interesante del tipado dinámico, es lo que se conoce como el duck typing (tipado de patos) y es que, si hay algo que camina como un pato y habla como un pato, lo más probable es que sea un pato. El tipado dinámico es interesante utilizarlo cuando se desconoce a ciencia cierta el tipo de datos de una determinada variable o parámetro. Esto nos proporciona un gran nivel de reutilización de nuestro código, así como la posibilidad de implementar funciones genéricas.
1.5.4. Trabajo con cadenas
Groovy nos facilita el trabajo con las cadenas de texto en mayor medida que lo hace Java, añadiendo su propia librería groovy.lang.GString, con lo que además de los métodos ofrecidos por la clase java.lang.String, Groovy cuenta con más metodos ofrecidos por esta librería. Una característica de como trabaja Groovy con las cadenas de texto es que nos permite introducir variables en las cadenas sin tener que utilizar caracteres de escape como por ejemplo "hola $minombre", donde en la misma cadena se introduce el valor de una variable. Esto es típico de algunos lenguajes de programación como PHP y facilita la lectura de nuestro código ya que no tenemos que estar escapando constantemente.
La siguiente tabla muestra las diferentes formas que hay en Groovy para crear una cadena de texto:
Caracteres utilizados | Ejemplo | Soporte GString |
---|---|---|
Comillas simples |
'hola Juan' |
No |
Comillas dobles |
"hola $nombre" |
Sí |
3 comillas simples |
'''-------------<br/>Total:0.02<br/>-------------''' |
No |
3 comillas dobles |
"""-------------<br/>Total:$total<br/>-------------""" |
Sí |
Símbolo / |
/x(\d*)y/ |
Sí |
La diferencia entre las comillas simples y las dobles es la posibilidad de incluir variables precedidas del símbolo $ para mostrar su valor. Las cadenas introducidas con el símbolo / son utilizadas con expresiones regulares, como veremos más adelante.
La librería GString
La librería GString (groovy.lang.GString) añade determinados métodos para facilitarnos el trabajo con cadenas de texto, las cuales normalmente se crean utilizando comillas dobles. Básicamente, una cadena de tipo GString nos va a permitir introducir variables precedidas del símbolo $. También es posible introducir expresiones entre llaves (${expresion}), tal y como si estuviéramos escribiendo un closure. Veamos algunos ejemplos:
nombre = 'Fran'
apellidos = 'García'
salida = "Apellidos, nombre: $apellidos, $nombre"
fecha = new Date(0)
salida = "Año $fecha.year, Mes $fecha.month, Día $fecha.date"
salida = "La fecha es ${fecha.toGMTString()}"
sentenciasql = """
SELECT nombre, apellidos
FROM usuarios
WHERE anyo_nacimiento=$fecha.year
"""
Ahora que ya podemos declarar variables de texto, vamos a ver algunos métodos que podemos utilizar en Groovy:
saludo = 'Hola Juan'
assert saludo.startsWith('Hola')
assert saludo.getAt(3) == 'a'
assert saludo[3] == 'a'
assert saludo.indexOf('Juan') == 5
assert saludo.contains('Juan')
assert saludo[5..8] == 'Juan'
assert 'Buenos días' + saludo - 'Hola' == 'Buenos días Juan'
assert saludo.count('a') == 2
assert 'b'.padLeft(3) == ' b'
assert 'b'.padRight(3,'_') == 'b__'
assert 'b'.center(3) == ' b '
assert 'b' * 3 == 'bbb'
Expresiones regulares
Las expresiones regulares nos permiten especificar un patrón y buscar si éste aparece en un fragmento de texto determinado. Groovy deje que sea Java la encargada del tratamiento de las expresiones regulares, pero además, añade tres métodos para facilitarnos este trabajo:
-
El operador =~: find
-
El operador ==~: match
-
El operador ~String: pattern
Con los patrones de las expresiones regulares, realmente estamos indicando que estamos buscando exactamente. Veamos algunos ejemplos:
Patrón | Significado |
---|---|
algo de texto |
simplemente encontrará la frase "algo de texto" |
algo de\s+texto |
encontrará frases que empiecen con "algo de", vayan seguidos por uno o más caracteres y terminen con la palabra texto |
\d\d/\d\d/\d\d\d\d |
detectará fechas como por ejemplo 28/06/2008 |
El punto clave de los patrones de las expresiones regulares, son los símbolos, que los podemos sustituir por determinados fragmentos de texto. La siguiente tabla presenta estos símbolos:
Símbolo | Significado |
---|---|
. |
Cualquier carácter |
^ |
El inicio de una línea |
$ |
El final de una línea |
\d |
Un dígito |
\D |
Cualquier cosa excepto un dígito |
\s |
Un espacio en blanco |
\S |
Cualquier cosa excepto un espacio en blanco |
\w |
Un carácter de texto |
\W |
Cualquier carácter excepto los de texto |
\b |
Límite de palabras |
() |
Agrupación |
(x|y) |
O x o y |
x* |
Cero o más ocurrencias de x |
x+ |
Una o más ocurrencias de x |
x? |
Cero o una ocurrencia de x |
x{m,n} |
Entre m y n ocurrencias de x |
x{m} |
Exactamente m ocurrencias de x |
[a-d] |
Incluye los caracteres a, b, c y d |
[^a] |
Cualquier carácter excepto la letra a |
Las expresiones regulares nos ayudarán en Groovy a:
-
Indicarnos si un determinado patrón encaja completamente con un texto
-
Si existe alguna ocurrencia de un patrón en una cadena
-
Contar el número de ocurrencias
-
Hacer algo con una determinada ocurrencia
-
Reemplazar todas las ocurrencias con un determinado texto
-
Separar una cadena en múltiples cadenas a partir de las ocurrencias que aparezcan en la misma
El siguiente fragmento de código muestra el funcionamiento básico de las expresiones regulares.
refran = "tres tristes tigres tigraban en un tigral"
//Compruebo que hay al menos un fragmento de código que empieza por t,
//le siga cualquier caracter y posteriormente haya una g
assert refran =~ /t.g/
//Compruebo que el refrán esté compuesto sólo
//por palabras seguidas de un espacio
assert refran ==~ /(\w+ \w+)*/
//Compruebo que el valor de una operación de tipo match es un booleano
assert (refran ==~ /(\w+ \w+)*/) instanceof java.lang.Boolean
//A diferencia que una operación de tipo find,
//las operaciones match se evalúan por completo contra una cadena
assert (refran ==~ /t.g/) == false
//Sustituyo las palabras por el caracter x
assert (refran.replaceAll(/\w+/,'x')) == 'x x x x x x x'
//Devuelve un array con todas las palabras del refrán
palabras = refran.split(/ /)
assert palabras.size() == 7
assert palabras[2] == 'tigres'
assert palabras.getAt(3) == 'tigraban'
Es importante resaltar la diferencia entre el operador find y el operador match. El operador match es más restrictivo puesto que intenta hacer coincidir un patrón con la cadena entera, mientras que el operador find, sólo pretende encontrar una ocurrencia del patrón en la cadena.
Ya sabemos como localizar fragmentos de texto en cadenas, pero ¿y si queremos hacer algo con estas cadenas encontradas? Groovy nos vuelve a facilitar esta tarea y pone a nuestra disposición un par de formas para recorrer las ocurrencias encontradas: each y eachMatch. Por un lado, al método eachMatch se le pasa una cadena con un patrón de expresión regular como parámetro: String.eachMatch(patron), mientras que al método each se le pasa directamente el resultado de una operación de tipo find() o match(): Matcher.each(). Veámos ambos métodos en funcionamiento.
refran = "tres tristes tigres tigraban en un tigral"
//Busco todas las palabras que acaben en 'es'
rima = ~/\b\w*es\b/
resultado = ''
refran.eachMatch(rima) { match ->
resultado += match + ' '
}
assert resultado == 'tres tristes tigres '
//Hago lo mismo con el método each
resultado = ''
(refran =~ rima).each { match ->
resultado += match + ' '
}
assert resultado == 'tres tristes tigres '
//Sustituyo todas las rimas por guiones bajos
assert (refran.replaceAll(rima){ it-'es'+'__'} == 'tr__ trist__ tigr__ tigraban en un tigral')
1.5.5. Números
El GDK de Groovy introduce algunos métodos interesantes en cuanto al tratamiento de números. Estos métodos funcionan como closures y nos servirán como otras formas de realizar bucles. Estos métodos son:
-
times(), se utiliza para realizar repeticiones
-
upto(), utilizado para realizar una secuencia de acciones de forma creciente
-
downto(), igual que el anterior pero de forma decreciente
-
step(), es el método general para realizar una secuencia paso a paso
Pero como siempre, veamos varios ejemplos.
def cadena = ''
10.times {
cadena += 'g'
}
assert cadena == 'gggggggggg'
cadena = ''
1.upto(5) { numero ->
cadena += numero
}
assert cadena == '12345'
cadena = ''
2.downto(-2) { numero ->
cadena += numero + ' '
}
assert cadena == '2 1 0 -1 -2 '
cadena = ''
0.step(0.5, 0.1) { numero ->
cadena += numero + ' '
}
assert cadena == '0 0.1 0.2 0.3 0.4 '
1.6. Colecciones
Ahora que ya hemos introducido los tipos de datos simples, llega el turno de hablar de las colecciones presentes en Groovy. En este apartado vamos a ver tres tipos de datos. Por un lado, las listas y los mapas, que tienen prácticamente las mismas connotaciones que en Java, con alguna nueva característica que añade Groovy, y por otro, los rangos, un concepto que no existe en Java.
1.6.1. Rangos
Empecemos por lo novedoso. Cuantas veces no nos habremos encontrado con un bloque de código similar al siguiente
for (int i=0;i<10;i++){
//hacer algo con la variable i
}
El anterior fragmento de código se ejecutará empezando en un límite inferior (0) y terminará de ejecutarse cuando la variable i llegue al valor 10. Uno de los objetivos de Groovy consiste en facilitar al programador la lectura y la comprensión del código, así que los creadores de Groovy pensaron que sería útil introducir el concepto de rango, el cual tendría por definición un límite inferior y uno superior.
Para especificar un rango, simplemente se escribe el límite inferior seguido de dos puntos y el límite superior, limiteInferior..limiteSuperior. Este rango indicaría que ambos valores establecidos están dentro del rango, así que si queremos indicarle que el límite superior no está dentro del rango, debemos utilizar el operador ..<, limiteInferior..<limiteSuperior. También existen los rangos inversos, en los que el límite inferior es mayor que el límite superior. Veamos algunos ejemplos:
//Rangos inclusivos
assert (0..10).contains(5)
assert (0..10).contains(10)
//Rangos medio-exclusivos
assert (0..<10).contains(9)
assert (0..<10).contains(10) == false
//Comprobación de tipos
def a = 0..10
assert a instanceof Range
//Definición explícita
a = new IntRange(0,10)
assert a.contains(4)
//Rangos para fechas
def hoy = new Date()
def ayer = hoy - 1
assert (ayer..hoy).size() == 2
//Rangos para caracteres
assert ('a'..'f').contains('e')
//El bucle for con rangos
def salida = ''
for (elemento in 1..5){
salida += elemento
}
assert salida == '12345'
//El bucle for con rangos inversos
salida = ''
for (elemento in 5..1){
salida += elemento
}
assert salida == '54321'
//Simulación del bucle for con rangos inversos
//y el método each con un closure
salida = ''
(5..<1).each { elemento ->
salida += elemento
}
assert salida == '5432'
Los rangos son objetos y como tales, pueden ser pasados como parámetros a funciones o bien ejecutar sus propios métodos. Un uso interesante de los rangos es el filtrado de datos. También es interesante verlos como clasificador de grupos y su utilidad se puede comprobar en los bloques de código switch.
//Rangos como clasificador de grupos
edad = 31
switch (edad){
case 16..20: interesAplicado = 0.25; break
case 21..50: interesAplicado = 0.30; break
case 51..65: interesAplicado = 0.35; break
}
assert interesAplicado == 0.30
//Rangos para el filtrado de datos
edades = [16,29,34,42,55]
joven = 16..30
assert edades.grep(joven) == [16,29]
Como se ha podido comprobar, podemos especificar rangos para fechas e incluso para cadenas. En realidad, cualquier tipo de dato puede ser utilizado en un rango, siempre que se cumplan una serie de condiciones:
-
El tipo implemente los métodos next() y previous(), que sobrecargan los operadores ++ y --
-
El tipo implemente java.lang.Comparable, implementando el método compareTo() que sobrecarga el operador <⇒
1.6.2. Listas
En Java, agregar un nuevo elemento a un array no es algo trivial. Una solución es convertir el array a una lista del tipo java.util.List, añadir el nuevo elemento y volver a convertir la lista en un array. Otra solución pasa por construir un nuevo array del tamaño del array original más uno, copiar los viejos valores y el nuevo elemento. Eso es la parte negativa de los arrays en Java. La parte positiva es que nos permite trabajar con índices en los arrays para recuperar su información, así como modificar su valor, como por ejemplo, miarray[indice] = nuevoelemento. Groovy se aprovecha de la parte positiva de Java en este sentido, y añade nuevas características para mejorar su parte negativa.
La definición de una lista en Groovy se consigue utilizando los corchetes [] y especificando los valores de la lista. Si no especificamos ningún valor entre los corchetes, declararemos una lista vacía. Por defecto, las listas en Groovy son del tipo java.util.ArrayList. Podemos rellenar fácilmente las listas a partir de otras con el método addAll(). También se pueden definir listas a partir de otras con el constructor de la clase LinkedList.
miLista = [1,2,3]
assert miLista.size() == 3
assert miLista[2] == 3
assert miLista instanceof ArrayList
listaVacia = []
assert listaVacia.size() == 0
listaLarga = (0..1000).toList()
assert listaLarga[324] == 324
listaExplicita = new ArrayList()
listaExplicita.addAll(miLista)
assert listaExplicita.size() == 3
listaExplicita[2] = 4
assert listaExplicita[2] == 4
listaExplicita = new LinkedList(miLista)
assert listaExplicita.size() == 3
listaExplicita[2] = 4
assert listaExplicita[2] == 4
En el fragmento de código anterior, hemos visto como se puede especificar un valor a un elemento de la lista. Pero, ¿qué pasa si queremos especificar un mismo valor a toda la lista o un trozo de la misma? Los creadores de Groovy ya han pensado en ese problema y podemos utilizar rangos y colecciones en las listas.
miLista = ['a','b','c','d','e','f']
assert miLista[0..2] == ['a','b','c']//Acceso con Rangos
assert miLista[0,2,4] == ['a','c','e']//Acceso con colección de índices
//Modificar elementos
miLista[0..2] = ['x','y','z']
assert miLista == ['x','y','z','d','e','f']
//Eliminar elementos de la lista
miLista[3..5] = []
assert miLista == ['x','y','z']
//Añadir elementos a la lista
miLista[1..1] = ['y','1','2']
assert miLista == ['x','y','1','2','z']
miLista = []
//Añado objetos a la lista con el operador +
miLista += 'a'
assert miLista == ['a']
//Añado colecciones a la lista con el operador +
miLista += ['b','c']
assert miLista == ['a','b','c']
miLista = []
miLista << 'a' << 'b'
assert miLista == ['a','b']
assert miLista - ['b'] == ['a']
assert miLista * 2 == ['a','b','a','b']
En ocasiones las listas son utilizadas juntos a estructuras de control para controlar el flujo de nuestro programa.
miLista = ['a','b','c']
//Listas como clasificador de grupos
letra = 'a'
switch (letra){
case miLista: assert true; break;
default: assert false
}
//Listas como filtrado de datos
assert ['x','y','a'].grep(miLista) == ['a']
//Bucle for con lista
salida = ''
for (i in miLista){
salida += i
}
assert salida == 'abc'
Las listas tienen una larga lista de métodos disponibles en el API de Java en la interfaz java.util.List para por ejemplo ordenar, unir e intersectar listas.
1.6.3. Mapas
Un mapa es prácticamente igual que una lista, con la salvedad de que los elementos están referenciados a partir de una clave única (sin caracteres extraños ni palabras reservadas por Groovy), miMapa['clave'] = valor. Podemos especificar un mapa al igual que lo hacíamos con las listas utilizando los corchetes, pero ahora debemos añadir la clave a cada valor introducido, como por ejemplo miMapa = [a:1, b:2, c:3]. Los mapas creados implícitamente son del tipo java.util.HashMap. Veámoslo con ejemplos:
def miMapa = [a:1, b:2, c:3]
assert miMapa instanceof HashMap
assert miMapa.size() == 3
assert miMapa['a'] == 1
//Definimos un mapa vacio
def mapaVacio = [:]
assert mapaVacio.size() == 0
//Definimos un mapa de la clase TreeMap
def mapaExplicito = new TreeMap()
mapaExplicito.putAll(miMapa)
assert mapaExplicito['c'] == 3
Las operaciones más comunes con los mapas se refieren a la recuperación y almacenamiento de datos a partir de la clave. Veamos algunos métodos de acceso y modificación de los elementos de un mapa:
def miMapa = [a:1, b:2, c:3]
//Varias formas de obtener los valores de un mapa
assert miMapa['a'] == 1
assert miMapa.a == 1
assert miMapa.get('a') == 1
//Si no existe la clave, devuelve un valor por defecto, en este caso 0
assert miMapa.get('a',0) == 1
//Asignación de valores
miMapa['d'] = 4
assert miMapa.d == 4
miMapa.e = 5
assert miMapa.e == 5
Los mapas en Groovy utilizan los mismos métodos que los mapas en Java y éstos están en el API de Java referente a java.util.Map, pero además, Groovy añade un par de métodos llamados any() y every(), los cuales, utilizados como closures, permite evaluar si todos (every) o al menos uno (any) de los elementos del mapa cumplen una determinada condición. Además, en el siguiente fragmento de código, vamos a ver como iterar sobre los mapas.
def miMapa = [a:1, b:2, c:3]
def resultado = ''
miMapa.each { item ->
resultado += item.key + ':'
resultado += item.value + ', '
}
assert resultado == 'a:1, b:2, c:3, '
resultado = ''
miMapa.each { key, value ->
resultado += key + ':'
resultado += value + ', '
}
assert resultado == 'a:1, b:2, c:3, '
resultado = ''
for (key in miMapa.keySet()){
resultado += key + ':'
resultado += miMapa[key] + ', '
}
assert resultado == 'a:1, b:2, c:3, '
resultado = ''
for (value in miMapa.values()){
resultado += value + ' '
}
assert resultado == '1 2 3 '
def valor1 = [1, 2, 3].every { it < 5 }
assert valor1
def valor2 = [1, 2, 3].any { it > 2 }
assert valor2
Y para terminar con los mapas, vamos a ver otros métodos añadidos por Groovy para el manejo de los mapas, que nos permitirán:
-
Crear un submapa de un mapa dado a partir de algunas claves: subMap()
-
Encontrar todos los elementos de un mapa que cumplen una determinada condición: findAll()
-
Encontrar un elemento de un mapa que cumpla una determinada condición: find()
-
Realizar operaciones sobre los elementos de un mapa: collect()
def miMapa = [a:1, b:2, c:3]
def miSubmapa = miMapa.subMap(['a','b'])
assert miSubmapa.size() == 2
def miOtromapa = miMapa.findAll { entry -> entry.value > 1 }
assert miOtromapa.size() == 2
assert miOtromapa.c == 3
def encontrado = miMapa.find { entry -> entry.value < 3}
assert encontrado.key == 'a'
assert encontrado.value == 1
def miMapaDoble = miMapa.collect { entry -> entry.value *= 2}
//Todos los elementos son pares
assert miMapaDoble.every { item -> item % 2 == 0 }
1.7. Ejercicios
1.7.1. De Java a Groovy (0.25 puntos)
Modificar la siguiente clase en Java para convertirla en una clase en Groovy y que quede lo más simplificada posible con la misma funcionalidad.
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Todo {
private String titulo;
private String descripcion;
public Todo() {}
public Todo(String tit, String des) {
this.titulo = tit;
this.descripcion = des;
}
public String getTitulo(){
return titulo;
}
public void setTitulo(String tit){
this.titulo = tit;
}
public String getDescripcion(){
return descripcion;
}
public void setDescripcion(String des){
this.descripcion = des;
}
public static void main (String[] args){
List todos = new ArrayList();
todos.add(new Todo("Lavadora","Poner lavadora"));
todos.add(new Todo("Impresora","Comprar cartuchos impresora"));
todos.add(new Todo("Películas","Devolver películas videoclub"));
for (Iterator iter = todos.iterator();iter.hasNext();) {
Todo todo = (Todo)iter.next();
System.out.println(todo.getTitulo()+" "+todo.getDescripcion());
}
}
}
1.7.2. GroovyBeans (0.50 puntos)
Crear la clase Libro con las propiedades mínimas necesarias que lo describan. La información a almacenar de estos libros será el nombre del libro, el año de edición y el autor del mismo.
Simplemente crearemos un constructor de la clase Libro que permita especificar las tres propiedades de la clase (nombre, año de edición y autor). Para comprobar que nuestra clase funciona correctamente, insertaremos los siguientes ejemplos de libros:
-
Nombre: La colmena, año de edición: 1951, autor: Cela Trulock, Camilo José
-
Nombre: La galatea, año de edición: 1585, autor: de Cervantes Saavedra, Miguel
-
Nombre: La dorotea, año de edición: 1632, autor: Lope de Vega y Carpio, Félix Arturo
Posteriormente comprueba que se han insertado correctamente cada uno de los libros indicados anteriormente mediante el uso de sentencias asserts y las funciones tipo getter() generadas automáticamente en Groovy
A continuación añade una nueva propiedad a la clase Libro para almacenar la editorial de los libros. Sin modificar la inserción de libros realizada anteriormente, inserta la editorial de cada uno de los libros utilizando los setters() definidos automáticamente por Groovy. Comprueba que los cambios se han efectuado correctamente nuevamente mediante la utilización de asserts.
Por último, modifica el comportamiento del <em>getter()</em> correspondiente a la propiedad autor para que modifique su comportamiento por defecto y devuelva en su lugar el autor del libro con el formato nombre y apellidos.
Como siempre, comprueba que el nuevo método funciona correctamente mediante la utilización de <em>assert</em>.
1.7.3. Expresiones regulares (0.5 puntos)
Crea un script en Groovy que permite agrupar las palabras que aparecen en un párrafo en función del número de letras que contengan. Almacenaremos este catálogo de palabras en un mapa cuya clave será el tamaño y el valor será una lista con todas las palabras de ese mismo tamaño.
El párrafo a analizar será el inicio de El Ingenioso Hidalgo Don Quijote de la Mancha:
En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lentejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las fiestas con sus pantuflos de lo mismo, los días de entre semana se honraba con su vellori de lo más fino. Tenía en su casa una ama que pasaba de los cuarenta, y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza, que así ensillaba el rocín como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años, era de complexión recia, seco de carnes, enjuto de rostro; gran madrugador y amigo de la caza. Quieren decir que tenía el sobrenombre de Quijada o Quesada (que en esto hay alguna diferencia en los autores que deste caso escriben), aunque por conjeturas verosímiles se deja entender que se llama Quijana; pero esto importa poco a nuestro cuento; basta que en la narración dél no se salga un punto de la verdad. |
Para comprobar si tu script funciona correctamente, puedes añadir estas líneas:
assert palabras[1] == ['y','a','o']
assert palabras[2] == ['en','un','de','la','no','ha','su','el','lo','se']
assert palabras[3] == ['que','los','una','más','las','con','sus','ama','así','era','hay','por','dél']
assert palabras[4] == ['cuyo','olla','algo','vaca','tres','sayo','para','días','fino','casa','mozo','como','edad','años','seco','gran','caza','esto','caso','deja','pero','poco']
assert palabras[5] == ['lugar','mucho','vivía','lanza','rocín','flaco','galgo','algún','resto','della','mismo','entre','tenía','campo','plaza','recia','amigo','decir','deste','llama','basta','salga','punto']
assert palabras[6] == ['mancha','nombre','quiero','tiempo','adarga','noches','duelos','partes','calzas','semana','pasaba','veinte','tomaba','carnes','enjuto','rostro','alguna','aunque','cuento','verdad']
assert palabras[7] == ['hidalgo','antigua','carnero','sábados','viernes','velarte','velludo','fiestas','honraba','vellori','sobrina','llegaba','frisaba','nuestro','quieren','quijada','quesada','autores','quijana','importa']
assert palabras[8] == ['corredor','salpicón','lentejas','palomino','domingos','hacienda','cuarenta','podadera','escriben','entender']
assert palabras[9] == ['acordarme','astillero','añadidura','consumían','concluían','pantuflos','ensillaba','cincuenta','narración']
assert palabras[10] == ['quebrantos','complexión','madrugador','diferencia','conjeturas']
assert palabras[11] == ['sobrenombre', 'verosímiles']