// // // // // // //

domingo, 11 de enero de 2015

Interfaces funcionales, Clases anónimas y Lambdas

    Todo el mundo sabe que existen las interfaces pero… ¿Sabemos cuándo usarlas? Aquí mostraré uno de sus usos: Las Interfaces funcionales. También aprovecharé para hacer una pequeña introducción a las Lambdas.

  Una interface funcional es una interface que solo define un método, que es todo lo que importa de la interfaz, el Algoritmo.Es una manera de trabajar con Métodos como si fueran Objetos. Por ejemplo, imaginemos que definimos una interface como la siguiente, que define un método con dos números:
public interface Prueba {
   
    public float metodoConDosNumeros(float n1, float n2);
}
    Y, ahora, hagamos dos clases que la implementen, una para que calcule la suma y otra la multiplicación.
class Suma implements Prueba {

    @Override
    public float metodoConDosNumeros(float n1, float n2) {
        return n1 + n2;
    }
}

class Multiplicacion implements Prueba {

    @Override
    public float metodoConDosNumeros(float n1, float n2) {
        return n1 * n2;
    }
}
    Ahora un ejemplo de uso:
public static void main(String[] args) {

        ArrayList<Prueba> algoritmosDosNumeros = new ArrayList();
     
        algoritmosDosNumeros.add(new Suma());
        algoritmosDosNumeros.add(new Multiplicacion());
     
        Collections.shuffle(algoritmosDosNumeros);
     
        System.out.println(algoritmosDosNumeros.get(0).metodoConDosNumeros(2, 3));
     
}
    Como veis, desordeno la colección, así que solo en tiempo de ejecución se sabrá cuál de los dos se ejecutará. Ahora bien, es un poco incómodo andar creando clases para esto ¿no?, Es aquí donde entran las clases anónimas. Una clase anónima es una clase que se crea directamente en el momento de instanciar  una interface o una clase abstracta, veamos como:
algoritmosDosNumeros.add(new Prueba() {
            @Override
            public float metodoConDosNumeros(float n1, float n2) {
             
                return n1 - n2;
            }
});
    Estoy creando un new Prueba(), pero me diréis que al ser una interfaz no se puede hacer eso, y no se puede, excepto si implementamos allí directamente todos sus métodos, como en el ejemplo. Pero es mucho código ¿Verdad?, pues usemos lambdas. La estructura de un lambda es como la de un método pero se escribe menos, veamos: Primero, los argumentos se escriben así: (float  n1, float  n2) luego una flecha -> y después el cuerpo del método. En este caso, sería return n1/n2; pero en las lambdas no hace  falta poner return, asique quedaría así: (float n1, float n2) -> n1 - n2. Las lambdas sustituyen a las clases anónimas de la siguiente manera:
algoritmosDosNumeros.add((float n1, float n2) -> n1/ n2);
    Como se puede apreciar, pide los mismo argumentos y del mismo tipo que el método de la interfaz que representa, pero no hace falta que se llaman igual, por lo que: (float num1, float num2) -> num1/num2  funcionaría  exactamente igual. También se pueden omitir los tipos y poner: (num1, num2) -> num1/num2 , ya que el Compilador sabe que es lo que pide el método de la interfaz que se implementa. No obstante conviene ponerlos para mejorar la legibilidad.

    Ahora nuestro programa tiene la suma, resta, multiplicación y división implementadas de diferente manera, pero hasta que no se ejecute no se sabrá que hará con el 2  y con 3:
public static void main(String[] args) {

        ArrayList<Prueba> algoritmosDosNumeros = new ArrayList();
     
        algoritmosDosNumeros.add(new Suma());
        algoritmosDosNumeros.add(new Multiplicacion());
        algoritmosDosNumeros.add(new Prueba() {
            @Override
            public float metodoConDosNumeros(float n1, float n2) {
             
                return n1 - n2;
            }
        });
        algoritmosDosNumeros.add((float n1, float n2) -> n1/n2);
     
        Collections.shuffle(algoritmosDosNumeros);
     
        System.out.println(algoritmosDosNumeros.get(0).metodoConDosNumeros(2, 3));
     
}
   Las interfaces funcionales es algo que usamos a diario, por ejemplo Comparator<T> es una interfaz funcional que solo define compare, así que si queremos ordenar ordenar una colección de números podemos simplemente usar una lambda para ordenarlos de mayor a menor de esta manera:
ArrayList<Integer> num = new ArrayList();
     
        num.add(1);num.add(3);num.add(6);num.add(-1);num.add(10);
        Collections.sort(num, (Integer o1, Integer o2) -> o2-o1);
        //Así sería con la clase anónima:
        /*Collections.sort(num, new Comparator() {

            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });*/
        for(Integer i : num)
            System.out.println(i);
   El bucle for también podemos escribirlo como lambda con el método .stream(), que devuelve un Stream<Integer>, de una colección y el método .forEach(Consumer<Integer> c) del Stream. Como veis Consumer<T> es una interfaz funcional también así que cojamos los argumentos de su método que son (Integer i) luego la flecha -> y luego hagamos lo que queramos, en este caso: System.out.println(i);  Quedaría así:
// Se puede quitar el tipo del argumento y poner solo (I), porque el programa ya sabe      
// de qué tipo es, puesto que está definido en la interfaz
num.stream().forEach((Integer i) -> {
            System.out.println(i);
        });
Y si usáramos una clase anónima así:
num.stream().forEach(new Consumer<Integer>() {

            public void accept(Integer i) {
                System.out.println(i);
            }
        });
    Aquí podemos ver cómo está construida la interfaz. Como se puede observar, Java usa mucho las interfaces funcionales así que de nuestra mano queda usar clases, clases anónimas  o lambdas para implementarlas en nuestro código. El Compilador de Java se encargará de transcribir las lambdas a clases anónimas, que la JVM sí que sabe usar, por lo que usar clases anónimas o lambdas es nada más que para añadir legibilidad, porque el rendimiento es el mismo.
    Si trabajáis con NetBeans, cuando escribes una clase interna anónima, te propone, con una bombillita con un triángulo amarillo, convertirla automáticamente en una expresión lambda. Y, si pones el cursor dentro de una lambda, te propone convertirla en una clase anónima.




    Hasta aquí las interfaces funcionales y las lambdas, dentro de poco haré una pequeña entrada usando varias entradas en un mismo programa.

No hay comentarios:

Publicar un comentario