Introducción a Groovy (I)

Con este artículo comienza el tutorial de introducción a Groovy, paso previo a la próxima publicación de un tutorial de Grails (framework para desarrollo web basado en Groovy). El tutorial estará formado por lo siguientes capítulos:

  • Introducción a Groovy, sintaxis y características básicas del lenguaje
  • Strings, GStrings, Heredocs y Closures
  • Rangos y Colecciones
  • POGO's
  • Metaprogramación

Para poder seguir el tutorial es necesario tener instalado Groovy en tu equipo, misión que puedes llevar a cabo de forma sencilla siguiendo los pasos del siguiente anexo. Podrás acceder al resto de artículos de esta serie desde el archivo.

1.1 ¿Qué es Groovy?

Groovy es un lenguaje dinámico, orientado a objetos, muy íntimamente ligado a Java. EL 99% del código Java existente puede ser compilado mediante Groovy, y el 100% del código Groovy es convertido en bytecode Java, y ejecutado en tu JVM de manera natural. Groovy simplifica la sintaxis de Java hasta lo realmente necesario para expresar lo que queremos hacer, y ademas añade una serie de métodos tremendamente útiles al JDK, convirtiendo multitud de tareas en un placer.

1.2 Sintaxis

La sintaxis de Groovy es una especie de código Java minimalista, eliminando la mayor parte del código "innecesario" (por innecesario nos referimos al código que no es estrictamente necesario para ejecutar la acción que deseamos). Esto, que a priori podría parecernos una dificultad, haciéndonos pensar que tenemos que aprender un nuevo lenguaje, no lo es, pues como se ha mencionado en la sección 1.1 casi todo el código Java puede ser compilado con Groovy. De esta manera, podemos introducirnos en Groovy poco a poco, utilizando su sintaxis exclusiva mientras la vamos aprendiendo y la sintaxis de Java el resto del tiempo. Esto es válido en una misma clase o método, de manera que la curva de aprendizaje del lenguaje es muy suave. Veamos un ejemplo:

println "¡Hola Mundo!"
	

El código anterior podría considerarse el ejemplo más simple de Groovy. Esa única línea muestra por pantalla el mensaje ¡Hola Mundo!. No necesitamos escribir una clase contenedora, ni un método public static void main(String[] args) que rodee el código (llamaremos a esto un script). No hay llamada a System.out, hemos ignorado los paréntesis en la llamada al método println así como el punto y coma al final de la línea. Aquí es donde estriba la sencillez de Groovy: reduciendo el código a lo estrictamente necesario. Si deseamos escribir los paréntesis, el punto y coma o ambos, podemos hacerlo: el código seguirá funcionando.

1.3 Scripts

Existen diversas maneras de ejecutar código Groovy. Una de ellas es mediante scripts. Puedes escribir el código de la sección 1.2 en un archivo con extensión .groovy (p.e. holamundo.groovy) y ejecutarlo desde línea de comandos así:

groovy holamundo.groovy
	

Esto compilará el archivo en bytecode Java y sera ejecutado "al vuelo" en memoria. Este archivo será una clase con un método main, dentro del cual se insertará el contenido del script que este fuera de cualquier método, más los métodos del script que se insertarán dentro de la propia clase.

1.4 Groovy Shell

Una segunda opción para ejecutar código Groovy es mediante Groovy Shell. Para lanzar el groovy shell escribimos en la línea de comandos:

groovysh
	

Hecho esto, el prompt del sistema operativo cambiará al prompt de Groovy y podremos escribir código directamente en él. Para obtener ayuda, podemos escribir lo soguiente una vez que estamos dentro del shell:

help
	

Una característica interesante del shell es que mantiene un historial de todos los comandos que hemos introducido anteriormente, incluso aquellos de una ejecución anterior del shell. Para movernos por ese historial, podemos usar las flechas arriba-abajo del cursor de nuestro teclado.

Es necesario mencionar que el shell debe ser utilizado para ejecutar pequeños scripts, ya que algunas características mas complejas del lenguaje aún no están soportadas. Utilízalo como una manera rápida de probar un trozo de código que, tal vez, estas escribiendo dentro de tu IDE favorito.

1.5 Groovy Console

Una opción mas potente para ejecutar código Groovy es mediante la consola de Groovy. Esta consola funciona en modo gráfico y permite opciones mas potentes que el shell, como guardar y cargar archivos, opciones de edición de texto, etc. Para lanzar la consola de Groovy ejecuta lo siguiente desde la línea de comandos:

groovyconsole
	

La consola no padece de las limitaciones del shell a la hora de ejecutar ciertas características complejas del lenguaje. Mi recomendación es que sigas todos los ejemplos restantes del tutorial desde la consola, a menos que se especifique lo contrario.

1.6 Compilar archivos de fuentes

Todo archivo .groovy, ya contenga una clase o un script, puede ser compilado en un archivo .class. Este archivo es una clase Java normal que, por tanto, puede ser cargada desde Java y Groovy como cualquier otra clase. Realicemos un sencillo ejemplo para comprobarlo. Compila el archivo que hemos escrito en la sección 1.3 de la siguiente manera:

groovyc holamundo.groovy
	

Si miramos en el sistema de archivos, veremos que junto al archivo fuente 'holamundo.groovy' también aparece su archivo binario 'holamundo.class'. Ahora crea un script nuevo, en un fichero llamado 'ejecutor.groovy' y escribe dentro de él lo siguiente:

hm = new holamundo() 
hm.main() 
println "Este es otro archivo" 
	

Ahora ejecutamos este último archivo desde línea de comandos...:

groovy ejecutor.groovy 
	

Obtenemos la siguiente salida:

¡Hola Mundo! 
Este es otro archivo 
	

Como puedes ver, hemos instanciado una clase que fue escrita y compilada en Groovy, resultando en una clase Java normal.

1.7 Importaciones automáticas

Groovy importa por defecto varios paquetes y clases, de manera que pueden ser utilizados inmediatamente sin necesidad de escribir sus correspondientes sentencias import. Dichos paquetes y clases son:

  • groovy.lang
  • groovy.util
  • java.lang
  • java.util
  • java.net
  • java.io
  • java.math.BigInteger
  • java.math.BigDecimal

1.8 Punto y coma opcional

Como ya hemos visto en los ejemplos anteriores, el carácter de punto y coma (;) al final de cada línea es opcional. Sólo debe ser usado al escribir varias sentencias en una única línea:

println "primera sentencia"; println "segunda sentencia"
	

1.9 Paréntesis opcional

El carácter de paréntesis para llamar a un método con argumentos también es opcional.

void metodo(String s1, String s2) { 
    println s1 + s2 
} 

metodo "uno", "dos" 
	

Los paréntesis si que son obligatorios cuando se llama a un método sin argumentos (ya que el compilador no podría saber si es un método o una variable lo que estamos llamando).

1.10 Return opcional

La sentencia return al final de un método también es opcional en Groovy. Si se omite, el resultado devuelto por su última línea será implícitamente su valor de retorno (esto también aplica a scripts, que como vimos en el punto 1.3, en tiempo de ejecución son convertidos en una clase que se ejecuta en memoria):

void cuadrado(int numero) { 
    println "devolviendo cuadrado de " + numero 
    numero*numero 
} 
	

El método anterior, cuando es ejecutado, escribe un mensaje por la salida estándar y devuelve el cuadrado del número pasado como argumento. La sentencia return solo es necesario cuando, por ejemplo, necesitemos devolver un valor dentro de un bucle o un bloque switch En caso de que la última sentencia de un método o script no devuelva ningún valor (por ejemplo una sentencia println o la llamada a un método void), se devolverá null.

1.11 Declaración de tipos opcional

En Groovy no tenemos que declarar el tipo de una variable cuando le asignamos un valor. Esto es debido a la naturaleza dinámica de Groovy, en contra de Java que es un lenguaje fuertemente tipado (o estático).

x = "Esto es un String"
println x.class
x = 12
println x.class
	

La variable x, que no ha sido declarada previamente en el código, es inicializada con un String primero y con un entero después. Las sentencias println demuestran esto mostrando por pantalla el tipo actual de la variable después de cada asignación. Esta naturaleza dinámica es conocida como duck typing, un término que traducido literalmente significa escritura de pato, y proviene de un dicho inglés que dice:

Si anda como un pato, come como un pato y hace ruido como un pato, entonces es un pato.

En nuestro caso, si una variable contiene un String y se comporta como tal, entonces es un String (aunque no hayamos definido su tipo). Formalmente, esta característica se denomina inferencia de tipos.

El código anterior, que funciona bien en scripts, no puede ser usado dentro de una clase: o declaramos el tipo explícitamente (String o int) o declaramos la variable con def:

class MiClase { 
    def x = "Esto es un String" 
         
    void metodo() { 
        println x.class         
        x = 12 
        println x.class 
    } 
} 

new MiClase().metodo() 
	

Mi recomendación es utilizar siempre def, incluso en scripts, puesto que así podemos establecer en que momento estamos creando una variable y en que momento estamos modificando su valor (código más legible).

1.12 Manejo de excepciones

En Groovy todas las excepciones son de tipo no chequeadas, por lo que no tenemos que declararlas ni capturarlas. Esto nos da la liberad de manejarlas solo cuando creamos necesario hacerlo, en contra del sistema de manejo de excepciones en Java donde el compilador nos obliga a hacerlo siempre que un metodo declare lanzar una excepción de tipo chequeada. Veamos un ejemplo:

FileReader fr = new FileReader("actividad.log")
	

En el ejemplo anterior, si el archivo 'actividad.log' no existe se lanzará una excepción del tipo FileNotFoundException. Pero imaginemos que esa línea se llama desde una aplicación donde ese archivo siempre existe (porque se ha creado 5 líneas de código antes o porque al iniciar la aplicación se crea automáticamente), o que estamos llamando a un archivo del sistema operativo que, de igual manera, debe estar siempre presente en disco. En estos dos casos los bloques try-catch solo añaden "ruido" al código, pues son a todas luces innecesarios. Groovy nos permite no declarar ni capturar cualquier excepción, sea del tipo que sea. Por supuesto que, cuando no podamos asegurar que una excepción no va a ser lanzada, deberemos gestionarla correctamente. Déjame recordarte que nunca debes capturar una excepción en un bloque catch vacío, pues tu aplicación podría estar funcionando mal y no ser consciente de ello.

1.13 Sobrecarga de operadores

Groovy permite la sobrecarga de operadores, de manera que podemos definir un método (dentro de una de nuestras clases) que será llamado al usar cierto operador. La manera mas sencilla de entenderlo es, como siempre, viendo un ejemplo:

class MiInteger { 
    private int valor = 0; 
     
    public MiInteger(int valor) { 
        this.valor = valor 
    } 
     
    public int plus(MiInteger otro) { 
        valor + otro.valor 
    } 
} 

def mc1 = new MiInteger(8) 
def mc2 = new MiInteger(7) 
println mc1 + mc2 
	

El ejemplo anterior es una clase wrapper simplificada de Integer. En su interior hay un método llamado plus() el cual sobrecarga el operador + (caracter más). Debajo de la definición de la clase hemos instanciado dos objetos de dicha clase y los hemos sumado con el citado operador +. El resultado ha sido la llamada al método plus(), el cual ha devuelto la suma de los valores contenidos en ambas instancias (recuerda que la última línea de un método es implícitamente una sentencia return. Puedes encontrar una lista con todos los operadores que pueden ser sobrecargados y sus métodos correspondientes en este enlace.

1.14 Referenciación segura

El operador de referenciación segura (?) evita que se lance la temida excepción de tipo NullPointerException cuando llamamos a un método en un objeto con valor null. En su lugar, la llamada al método devolverá null:

MiInteger mi
mi?.plus(new MiInteger(10))
	

El código anterior declara una instancia de la clase MiInteger y llama a su método plus() usando el operador de referenciación segura (el carácter de interrogación) antes del punto. Al ejecutar el código, obtenemos que la llamada al método devuelve null en lugar de lanzar una excepción NullPointerException.

El operador de referenciación segura puede ser encadenado tantas veces se desee:

empleado()?.getSueldo()?.getSalarioBase()
	

1.15 Autoboxing

En Groovy, todo es un objeto. Incluso los tipos primitivos son tratados como objetos. Mira el siguiente código:

println 2.floatValue()

El código anterior llama al método floatValue() que está presente en la clase Integer, a pesar de que el literal numérico 2 sería tratado en Java como un tipo primitivo. La razón de este comportamiento tan sorprendente como potente es que Groovy convierte (cuando es necesario) cualquier variable de tipo primitivo en su correspondiente clase wrapper.

1.16 Decimales

En Groovy, todas las expresiones numéricas con decimales son por defecto de tipo BigDecimal:

println 0.91.class 
	

Si ejecutas el código anterior, verás que la expresión decimal 0.91 es una instancia de la clase BigDecimal. Este comportamiento del lenguaje esta destinado a evitar la inexactitud de las clases Float y Double. Aunque dichas clases siguen estando disponibles, evidentemente deben ser instanciadas explícitamente:

println new Double(0.91).class
	

1.17 Verdadero y Falso

En Java, solamente true puede ser verdadero y solamente false puede ser falso. En Groovy el concepto de verdadero y falso ha sido extendido a multitud de situaciones más, siempre con la intención de simplificar el código y hacerlo tremendamente expresivo. Siempre que se evalúe un valor cero, null, un String vacío, una colección vacía, un array de longitud cero o un StringBuilder/StringBuffer vacío, se obtendrá false. En cualquier otra situación, se obtendrá true. Esto nos permite, por ejemplo, lo siguiente:

def lista = [] 

// ... 

if (lista) { 
    println "ArrayList con elementos" 
} else { 
    println "ArrayList vacio" 
} 
	

La primera línea del código anterior declara un ArrayList vacío, y después de varias líneas que no mostramos (que podrían añadir o eliminar objetos del ArrayList) se llama a una sentencia if pasándole como condición booleana la lista previamente creada. Si la lista contiene elementos, el bloque if será alcanzado; en caso contrario se ejecutará el bloque else.

Resumen

En este primer artículo del tutorial de introducción a Groovy hemos visto la sintaxis básica del lenguaje, así como algunas características interesantes como la sobrecarga de operadores, referenciación segura y autoboxing para tipos primitivos.

En el próximo artículo veremos como trabajar con cadenas de texto en Groovy, lo que nos permitirá introducir la clase GString (Groovy Strings) y los Heredocs. Adicionalmente nos introduciremos en el concepto de Closure, una de las características del lenguaje más potentes y totalmente necesaria para poder seguir con el resto del tutorial (ademas de elevar nuestro código hasta cotas inimaginables). Hasta entonces, be groovy!