Friday, March 21, 2014

Lo "nuevo" de Java 8

Oracle ha liberado recién la nueva versión de Java. En casi 20 años de historia el lenguaje dispone de 8 versiones y de innumerables actualizaciones para cada una de las que han existido. Sigo el lenguaje desde que nació y lo he usado para varias cosas, entre ellas para enseñar programación orientada a objetos cuando el sombrero que llevo puesto es el de profesor.
Escribo entre comillas la palabra nuevo para satisfacer a aquellos que consideran que no hay innovación alguna en las cosas que en la nueva versión se introducen. Casi siempre se cumple eso de que "no hay nada nuevo bajo el sol", pero siempre se recibe son agrado si los que diseñan el lenguaje que usas para ganarte el pan de cada día, incorporan a ese lenguaje las cosas buenas que otros han incorporado. Sucede aunque con menos frecuencia, que también se incorporan cosas malas.
Oracle siempre documenta lo nuevo de Java categorizando en varios rubros lo que incorpora al lenguaje. Voy a tomar esa categorías  para comentar aquí un resumen de lo incorporado y en este post comentaré lo que concierne al lenguaje. En posts subsiguientes me referiré a otras cosas, y en otros que sigan si no canso al lector ilustraré todas esas "novedades" con ejemplos.

Expresiones lambda


Son bastantes los lenguajes que han incorporado las expresiones lambda en su diseño. Lenguajes algo lejanos a la programación funcional como C++ y C# ya lo han hecho y casi todos los lenguajes que hoy se inventan tienen esa construcción como uno de sus requerimientos. Sin entrar mucho en el meollo de toda la teoría que rodea al cálculo lambda, podemos con cierto riesgo decir que una expresión lambda es un bloque de código que puede ser manipulado como una función sin necesidad de atarla a un identificador y que es posible manipularla en el lenguaje como se manipula cualquier otro valor u objeto si así se prefiere entender (funciones como ciudadanos de primera clase). Se usan varios términos para referirse a lo mismo, entre ellos, función anónima, literal función y constante función son de los que más abundan.
De nada serviría dar soporte en el lenguaje a esa construcción si esos bloques no pudieran ser tratados como valores: asignarlos a variables, pasarlos como argumentos a funciones, ser retornados como funciones, etc. Y entre todas esas cosas, la que sobre sale es la de que el lenguaje permita diseñar funciones de orden superior (forma funcional, funcional, functor); funciones que al menos permiten recibir como entrada funciones y devolver funciones como salida. Una vez que se tiene ésto, es posible entonces enriquecer el diseño del programa con los famosos closures o clausuras, a los cuales me referiré en otro post.
Esa necesidad de pasar como argumento un bloque de código a un método de algún objeto siempre ha estado presente en las aplicaciones que se escriben con Java y cualquier otro lenguaje. El ejemplo que casi todo el mundo utiliza para hacer evidente esa necesidad es en el procesamiento de eventos y, en ese contexto casi siempre se toma a Swing como el escenario típico. Desde sus inicios, Java nos ha "obligado" a hacer eso con clases anónimas que implementan alguna interfaz funcional de manera directa o indirecta (indirecta cuando heredamos de alguna clase que ya brinda implementación por default para algunos métodos). Muchos consideran que esa técnica es demasiado verbosa, lo que puede ser cierto en algunos casos y falso en otras situaciones. Lo importante a destacar es que realmente lo que hacemos cuando usamos esa técnica es porque tenemos la necesidad de hacerle llegar al método un bloque de código que el objeto que procesa ese método requiere para completar su trabajo. En esencia, pasar una función o bloque de código que pueda ser tratado con independencia y al cual se le pueda asignar un tipo.
Sigue el ejemplo casi "canónico"

/*Suponemos la existencia del objeto boton y necesitamos añadir código  para procesar el evento de acción que dispara*/
boton.addActionListener(new  ActionListener() {
    public void actionPerformed(ActionEvent event) {
        System.out.println ("Presionaste el botón y puedes ver este mensaje en consola");
    }
});

Como es conocido, ActionListener es una interfaz funcional (la descripción de una acción específica que debe ser implementada por quien quiera usarla). Esa acción o código como queramos llamarla es lo que requiere el objeto boton para realizar su trabajo. Las expresiones lambda llegan en su auxilio y la porción de código anterior, podemos escribirla ahora así:

boton.addActionListener(event -> System.out.println("Presionaste el botón y puedes ver este mensaje en consola");

¿Qué hemos logrado? Varias cosas y lo fundamental es que logramos mayor claridad en nuestra intención, que no es otra que hacerle llegar al objeto el bloque de código que es lo que realmente necesita. Por supuesto que hay otras cosillas en eso que hemos escrito, como la libertad de expresar el parámetro de entrada al bloque de código sin tener que indicar su tipo. Ya era hora que Java incluyera algo de inferencia de tipos, cuando es evidente.

Referencias a métodos


Sucede con bastante frecuencia que las expresiones lambda que escribimos, no hacen otra cosa que llamar a un método existente, bien de instancia o de clase. Podríamos entonces bautizar  esa expresión lambda con el nombre del método que ella invoca, y usar ese nombre para referirnos a ella. Sigue un ejemplo


/*IntPredicate es una interfaz funcional que usa el método test para comprobar la veracidad del predicado*/
interface IntPredicate {
     boolean test(int n);
}

El predicado IntPredicate se escribe para comprobar algunas de las propiedades de los números enteros. Usamos en el ejemplo la que escribimos y no la que ofrece el lenguaje. El propósito es no tener que meternos por lo pronto en los métodos de extensión virtual que esta última tiene.
Escribimos ahora una implementación de IntPredicate para comprobar si el parámetro del método test( int) es impar.

class OddPredicate implements IntPredicate {
    boolean test(int n) {
        return n %2 != 0;
    }
}


Escribimos ahora un filtro para crear una nueva lista que contenga solamente los números que satisfagan el predicado. El código es:

static List filter(IntPredicate, List from) {
    List to = new ArrayList<>();
    from.stream().filter( (item) -> (predicate.test(item) ) ).foreach( (item) -> { to.add(item) } );
    return to;

Escribimos ahora el código para filtrar los números enteros:


IntPredicate myPredicate = new OddPredicate();
List numeros = Arrays.asList(1,2,3,4,5,6,7,8,9);

/* Si no existieran referencias a métodos */
List impares1 =  filter(n -> obj.test(n) ,  numeros);

/*Pero es más "legible" usando referencias a métodos. Nos referimos a ese método con obj::test */
List impares2 = filter( obj::test, numeros);


Otras "novedades" del lenguaje en Java 8 son: los métodos de default o métodos de extensión virtuales, mejoras en las anotaciones, un sistema de inferencia de tipos mejorado y algunas cosillas para usar reflexión sobre los parámetros de los métodos. Lo discuto en el post que seguirá a éste.

No comments: