1. Introducción

En marzo de 2014 se publicó la nueva versión de la plataforma Java 8 lo cual represento una evolución notable en este lenguaje de programación. Para mas información dejo los siguientes enlaces.


2. Interfaz Stream

Stream en Java 8 es una alternativa mucho mas conveniente para poder iterar sobre una colección de datos de una manera declarativa. La ventaja de los Stream es que pueden procesarse de forma secuencial o paralela.

Con respecto a las operaciones que se realizan son de 2 tipos.

  • Operaciones intermedias: realizan filtrado o transformación.
  • Operacion terminal: producen un resultado.

Las operaciones intermedias son lazy porque se realizan cuando se invoca a la operacion final y no necesitan en algunos casos procesar todos los elementos del stream para obtener un resultado.

Con respecto a la iteracion sobre los elementos de un stream se expresa de forma interna a diferencia de una coleccion donde lo expresamos de forma externa.

List<integer> numbersGreaterThan5 = numbers  
        .stream()
        .filter(number -> number > 5)
        .filter(number -> number % 2 == 0)
        .collect(Collectors.toList());

3. filter(Predicate)

El método filter recibe un Predicate. Sirve para devolver otro Stream con sólo aquellos elementos que cumplen el predicado.

  • Filtrar todos los numeros que son mayores que 5.
  • Filtrar todos los numeros pares.
        public static List<Integer> filterAllNumbersGreaterThan5AndDividedBy2(List<Integer> numbers) {
            List<Integer> numbersGreaterThan5AndDividedBy2 = numbers
                                .stream()
                                .filter(number -> number > 5)
                                .filter(number -> number % 2 == 0)
                                .collect(Collectors.toList());
        return numbersGreaterThan5AndDividedBy2;
    }

4. map(Function)

El método map recibe un Function. Tiene sus adaptaciones mapToInt (ToIntFunction), mapToLong (ToLongFunction) y mapToDouble (ToDoubleFunction) devolviendo un Stream de objetos de otro tipo obtenidos a partir del tipo base aplicando una Function.

  • Multiplicar cada numero por 2.
  • Transformar cada numero a un string.
        public static List<String>  multiplyEachElementBy2UsingLambdaExpression(List<Integer> numbers) {
        Function<Integer, Integer> multiplyBy2 = number -> number * 2;
        Function<Integer, String> transformIntoString = number -> String.valueOf(number);

        List<String> multipliedNumbersAsString = numbers
            .stream()
            .map(multiplyBy2)
            .map(transformIntoString)
            .collect(Collectors.toList());

        return multipliedNumbersAsString;

    }

5. sorted(Comparator)

El método sorted recibe un Comparator. Ésta misma interfaz Comparator tiene algunos métodos que nos serán de gran ayuda

  • comparingInt() Permite comparar elementos de tipo int
  • comparingDouble() Permite comparar elementos de tipo double
  • comparingLong() Permite comparar elementos de tipo long
  • thenComparing() Permite anidar comparaciones. Útil cuándo deseamos ordenar por más de 1 atributo.

Lo mejor será revisar la documentación de la interfaz Comparator y otros ejemplos Comparator-Comparing

  • Listar ordenada.
  • Lista ordenada utilizando comparator.
        public static List<String> sortTheList(List<String> listOfWords)  {

        List<String> sortedList = listOfWords
            .stream()
            .sorted()
            .collect(Collectors.toList());

        return sortedList;
    }

    public static List<String> sortTheListWithInversedComparator(List<String> listOfWords)  {

        Comparator<String> inversed = (String o1, String o2) -> o2.compareTo(o1);

        List<String> sortedList = listOfWords
            .stream()
            .sorted(inversed)
            .collect(Collectors.toList());

        return sortedList;
    }

6. match(Predicate)

El método match recibe un Predicate. Hay de 3 tipos.

  • anyMatch() Devuelve un valor de cierto si algún elemento de una colección cumple una condición.
  • allMatch() Devuelve un valor de cierto si todos los elementos de una colección cumplen una condición
  • noneMatch Devuelve un valor de cierto si ninguno de los elemento de una colección cumple una condición.

    • Comprobar si hay algun numero mayor que 4.
    • Comprobar si todos lo numeros son pares.
    • Comprobar si hay un numero no par.
        public static boolean checkIfThereIsANumberGreaterThan4(List<Integer> numbers)  {

            boolean anyNumberGreaterThan4 = numbers
            .stream()
            .anyMatch(number -> number > 4);

        return anyNumberGreaterThan4;
    }

    public static boolean checkIfEachNumberIsPair(List<Integer> numbers)  {

        boolean eachNumberIsPair = numbers
            .stream()
            .allMatch(number -> number % 2 == 0);

        return eachNumberIsPair;
    }

    public static boolean checkIfEachNumberIsNotPair(List<Integer> numbers)  {

        boolean eachNumberIsNotPair = numbers
            .stream()
            .noneMatch(number -> number % 2 == 0);

        return eachNumberIsNotPair;
    }

7. limit(long n)/skip(long n)

Los métodos limit y skip recibes un long.

  • limit(long n) Devuelve una secuencia reducida de los primeros n elementos. Este método generará una excepción si n es negativo.
  • skip(long n) Devuelve una secuencia de elementos restantes después de saltar los primeros n elementos. Este método generará una excepción si n es negativo.
    public static List<Integer> getLimitNumbers(List<Integer> numbers)  {

        List<Integer> numberLimit = numbers
                .stream()
                .limit(3)
                .collect(Collectors.toList());


        return numberLimit;
    }

    public static List<Integer> getSkipNumbers(List<Integer> numbers)  {

        List<Integer> numberSkip = numbers
                .stream()
                .skip(3)
                .collect(Collectors.toList());

        return numberSkip;
    }

8. collect(Collectors)

Operaciones como sum, max, min, avg, group by, etc., Se especifican en el método collect.

  • Collectors.toList() Permite recopilar todos los elementos de un Stream en una instancia de List.
  • Collectors.counting() Permite contar todos los elementos de un Stream.
  • Collectors.summarizingDouble/Long/Int() Permite recopilar datos estadisticos (count, sum, min, max y average) sobre datos numericos.
  • Collectors.groupingBy() Permite agrupar elementos por alguna propiedad y almacenar resultados en una instancia de Map.
  • Collectors.averagingDouble/Long/Int() Permite obtener el promedio de los elementos.
  • Collectors.max()/min() Permite agrupar elementos por alguna propiedad y almacenar resultados en una instancia de Map

Lo mejor será revisar la documentación de la interfaz Collectors

Estos metodos lo aplicaremos ahora a un lista de objetos de tipo Product, La clase Product tiene los siguientes atributos.

   public class Product {
    private int id;
    private String name;
    private int supplier;
    private int category;
    private double unitPrice;
    private int unitsInStock;
    ....

  }

En una clase de nombre Util tenemos un metodo getProducts que retorna una lista de productos sobre el cual crearemos un Stream para aplicar las operaciones terminales y operacion final.

public static List<Product> getProducts(){

        List<Product>  products =  new ArrayList<>(); 
        products.add(new Product(1,"Product1",1,1,18.00,39));
        products.add(new Product(2,"Product2",1,2,19.00,40));
        products.add(new Product(3,"Product3",2,1,20.00,10));
        products.add(new Product(4,"Product4",2,2,30.00,20));
        products.add(new Product(5,"Product5",3,1,20.00,30));
        products.add(new Product(6,"Product6",3,2,15.00,15));

        return products;

    }

  }

8.1. Collectors.groupingBy()

  • Filtrar todos los productos que en almacen tengan menos de 20 unidades de stock y agrupados por unidades de stock,
    public static Map<Integer, List<Product>>  getFilterProductoGroupingByStock(List<Product> products)  {
            Map<Integer, List<Product>> collect = products.stream()
                    .filter(p -> p.getUnitsInStock() < 20)
                    .collect(Collectors.groupingBy(Product::getUnitsInStock));
            return collect;
    }

8.2. Collectors.counting()

  • Obtener el número de productos agrupados por proveedor.
    public static Map<Integer, Long>  getCountingProductoGroupingBySupplier(List<Product> products)  {
        Map<Integer, Long> collect = products.stream()
                .collect( 
                        Collectors.groupingBy( 
                                Product::getSupplier, 
                                Collectors.counting() 
                            )
                        );

        return collect;
    }

8.3. Collectors.summarizingDouble/Long/Int()

  • Obtener la suma del precio unitario de todos los productos agrupados por el número de stock en el almacen.
  • Obtener estas estadísticas respecto al precio unitario
    public static Map<Integer, Double> getSummatizingUnitPriceGroupingByStock(List<Product> products)  {

          Map<Integer, Double> collect = products.stream()
                .collect( 
                        Collectors.groupingBy( 
                                Product::getUnitsInStock, 
                                Collectors.summingDouble( 
                                        Product::getUnitPrice
                                )
                        )
                );
            return collect;
    }


    public static DoubleSummaryStatistics getStatisticsUnitPrice(List<Product> products)  {

        DoubleSummaryStatistics statistics =
                products.stream().collect(Collectors.summarizingDouble(Product::getUnitPrice));

            return statistics;
    }

8.4. Collectors.averagingDouble/Long/Int()

  • Obtener el promedio de stock en almacen
    public static Double getAveragingStock(List<Product> products)  {

        Double average = products.stream()
                .collect(Collectors.averagingInt(Product::getUnitsInStock));

                return average;
    }

8.5. Collectors.max()/min()

  • Producto con el precio unitario más alto
    public static Optional<Product> getMaxUnitPriceProduct(List<Product> products)  {

        Optional<Product> product = products.stream().max(Comparator.comparing(Product::getUnitPrice));

        return product;

    }

9. Codigo


10. Referencias