lunes, 2 de noviembre de 2015

Java 8 - Lambdas (colecciones)

Hola! Desgraciadamente llevo bastante tiempo sin escribir ninguna entrada en este blog el cual creé en su día para registrar aquellas cosillas que no quería olvidar, o que me parecieran interesantes, o que me gustaría tener de referencia 'a mi forma'... voy a tratar de recuperar esta sana costumbre así que hoy a escribir acerca de Java 8.

Java 8 lleva ya un tiempo publicado, concretamente desde Marzo de 2014, y aportó al lenguaje bastantes novedades, hoy vamos a ver una de ellas aplicadas a colecciones: Expresiones Lambdas.

Las expresiones Lambdas son un tipo de closure en java como lo han sido siempre la clases anónimas y nos acerca al mundo de la programación funcional. ¿Es Java un lenguaje funcional por tener lambdas? La respuesta es No, a java aún le queda mucho si es que algún día lo es para ser un lenguaje funcional pero lo que si es innegable es que este tipo de expresiones pueden hacer que nuestro código sea mucho mas legible y simple que antes, para ello vamos a ver un ejemplo:

Supongamos que tenemos una lista de 100000 elementos Strings que contienen números y queremos saber lo siguiente:
  • El valor medio de todos ellos
  • El numero de elementos que son mayores que un número dado

La solución a este problema pasaría por recorrer la lista de elementos convirtiéndolos a número y:
  • Almacenar la suma de todos los números y dividirla por el número de elementos para el valor medio.
  • Recorrer la lista y contar solo aquellos valores superiores al número dado.

Veamos el código a continuación:

package org.fran.anotherjavaprogrammer.lambda;

import java.time.Clock;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;

public class CollectionTest {

    private static final int MAX = 100_000;
 
    public static void main (String[] args) {
 
        final long start = Clock.tickSeconds(ZoneId.systemDefault()).millis();
        final List&ltString&gt list = new ArrayList<>();
  
        for (int i = 0; i < MAX; i++) {
            list.add(String.valueOf(i));
        }
  
        final long avg = getAverage(list);
  
        final long count = getCount(list, 1000);
  
        final long end = Clock.tickSeconds(ZoneId.systemDefault()).millis();
  
        System.out.println("Average value: " + avg);
        System.out.println("Elements number over 1000: " + count);
        System.out.println("Run time: " + (end-start) + " mls");
    }
 
    private static long getAverage(List<String> list) {
  
        long elementsSum = 0L;
        long average = 0L;
  
        if (list != null && list.size() > 0) {
            for (String elem : list) {
                final Integer e = Integer.parseInt(elem);
                elementsSum += e;
            }
   
            average = elementsSum / list.size();
        }
  
        return average;
  
    }
 
    private static int getCount(List<String> list, Integer lowLimit) {
  
        int count = 0;
  
        if (list != null && list.size() > 0) {
            for (String elem : list) {
                final Integer e = Integer.parseInt(elem);
    
                if (e >= lowLimit) {
                    count++;
                }
            }
        }
  
        return count;
  
    }
}

La salida del programa anterior es la siguiente

Average value: 49999
Elements number over 1000: 99000
Run time: 0 mls

Las expresiones Lambdas por el momento se pueden usar para ejecutar interfaces funcionales y para aquellas zonas de la API que hayan sido preparadas para ello como Collections. En Collections se nos ofrece un Stream a través del método stream mediante el cual podemos crear expresiones lambdas para realizar multitud de operaciones como reducciones, filtrados, recorridos, etc...

Veamos como queda el mismo código usando lambdas:

package org.fran.anotherjavaprogrammer.lambda;

import java.time.Clock;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;

public class CollectionLambdaTest {

    private static final int MAX = 100_000;
 
    public static void main (String[] args) {
 
        final long start = Clock.tickSeconds(ZoneId.systemDefault()).millis();
        final List<String> list = new ArrayList<>();
  
        for (int i = 0; i < MAX; i++) {
            list.add(String.valueOf(i));
        }
  
        final OptionalDouble avg = list.stream().
             mapToInt(s -> Integer.parseInt(s)).
             distinct().
             average();
  
        final long count = list.stream().
             mapToInt(s -> Integer.valueOf(s)).
             filter(n -> n >= 1000).
             count();
  
        final long end = Clock.tickSeconds(ZoneId.systemDefault()).millis();
  
        if (avg.isPresent()) {
            System.out.println("Average value: " + avg.getAsDouble());
        } else {
            System.out.println("Is not possible to calculate average value");
        }
  
        System.out.println("Elements number over 1000: " + count);
        System.out.println("Run Time: " + (end-start));
    }
}
El código usando lambdas es mucho mas compacto y legible que el original. Se observan varias cosas:
  • Se usa el separador '_' al definir MAX, esta novedad se introdujo en Java 7.
  • No se define el genérico en la parte derecha del new ArrayList al definir list, esto es otra característica introducida en Java 7, el compilador infiere está información de la parte izquierda de la asignación.

En el caso del cálculo de la media hacemos lo siguiente:
  • Hacemos una transformación de String a Int, devolviendo un IntSream con mapToInt, definiendo el parámetro ToIntFunction como una expresión lambda.
  • Seleccionamos los valores sin duplicados con distinct().
  • Finalmente, para todos los elementos calculamos la media con average().

En el caso de contar los elementos superiores a un numero dado hacemos lo siguiente:
  • Hacemos una transformación de String a Int, devolviendo un IntSream con mapToInt, definiendo el parámetro ToIntFunction como una expresión lambda.
  • Aplicamos un filtro en el cual seleccionamos los elementos que sean mayores o iguales al valor indicado. Definimos el parametro IntPredicate como una lambda.
  • Finalmente, contamos los elementos con count().

La salida del programa es la siguiente:
Average value: 49999.5
Elements number over 1000: 99000
Run Time: 0

En este ejemplo hemos podido ver como las expresiones lambdas se integran en Java a partir de la versión 8.

En este post podrás ver el uso de lambdas con interfaces funcionales.

Puedes descargar el código desde github.

No hay comentarios:

Publicar un comentario