Java 8 – Streams

java 8 - StreamsLos Streams son una secuencia de elementos que soportan operaciones de agregación secuencial y paralela. Una de las nuevas características de Java 8 que permite manipular las colecciones como nunca, casi parecido a un sueño.

Como programador seguramente has trabajado con una infinidad de colecciones, seguramente te ha tocado recorrerlas, ordenarlas, dividirlas, filtrarlas, eliminar o agregar nuevos elementos en la colección. Y cada vez que hacías esto estabas casi forzado a realizar un foreach sobre la colección, incluso, tenías que hacer un foreach anidado para poder realizar operaciones más complejas; prueba de ello es que el nombre ConcurrentModificationException  te recuerda a algo (¿o no?).

Pues bien, los Streams no permite crear atajos a la hora de procesar colecciones, creando flujos de datos que permite el procesamiento de una forma declarativa, es decir, que nos centramos en lo que queremos resolver, y no en cómo debemos hacerlo, como pasa en la programación imperativa.

Sígueme en mi canal de Youtube

Pero dejémonos de palabrerías (que me sobran) y vallamos a un escenario concreto donde se puede aplicar para entender mejor. Veamos el caso más simple al utilizar una colección, imprimamos todos los valores de una lista ¿Simple no?

public static void printForeach(){
  List<String> names = getStringArray();
  for (String name : names) {
     System.out.println(name);
  }
}

Este código que está viendo, es un escenario normal que utilizarías para imprimir todos los elementos de una lista (apuesto que sí), ¿verdad que no tiene nada de extraño? La verdad es que no, porque ya estamos acostumbrados a hacerlo así, pero te apuesto que tu punto de vista acerca de este código cambiaría si ti dijera que puedo hacer el foreach en una solo línea de código. ¿no me crees? Pues tómala!!

public static void printStream(){
    List<String> names = getStringArray();
    names.stream().forEach(System.out::println);
}

Probablemente te estés preguntado ¿qué clase de brujería es eso?, pero la realidad es que no hay tal brujería, simplemente estamos utilizando los Streams. Notemos que estamos utilizando el método stream() para obtener el Stream, seguido, solo utilizamos el método forEach() en el cual definimos mediante lo que vamos a hacer para cada elemento de la colección. En este caso estamos imprimiendo cada empleado utilizando Referencia a métodos.

Para comprender mejor que es un stream (espero no herir los sentimientos de algunos) lo voy a explicar de una forma simple fácil de digerir y que pueda que no cuadre con la definición perfecta, pero seguro me entenderás. Imagina que los streams son una forma de iterar todos los elementos de la colección de una forma simple, y que además mientras los iteras puedes realizar operaciones sobre la colección de forma declarativa. Es decir, vamos a realizar operaciones pero centrándonos en el objetivo que buscamos, no en cómo implementar el algoritmo para que funcione. Veamos otro ejemplo más complejo.

Imaginemos que tenemos una lista de Empleados, cada empleado tiene un ID que lo identifica como único, tiene un nombre y el departamento el que pertenece. La lista de empleados es la siguiente:

IDNameDepartment
1Oscar BlancarteSystems
2Liliana CastroSystems
3Fernanda MartinezSystems
4Manuel LopezRH
5Rebeca PerezSystems
1Oscar BlancarteSystems

Ahora bien, sobre esta lista queremos filtrar todos los Empleados que pertenecen al departamento de sistemas, luego vamos a ordenas todos los empleados de forma ascendente por su nombre, luego vamos a filtrar los elementos repetidos mediante su ID, finalmente, imprimiremos el nombre de todos los empleados que cumplieron con todas las reglas. Pfff!! ¿Imaginas hacer esto con una colección? A mí ya me empezó a doler la cabeza 🙁 . Por suerte, el enfoque declarativo de los Streams nos permite enfocarnos solamente en el que y no en el cómo. Veamos cómo quedaría la solución usando Streams.

public static void printOrderedSystemEmployees(){
    List<Employee> employess = getEmployeeArray();
    employess.stream()
            .filter(x -> x.getDepartment().equals("Systems"))
            .sorted((x,y) -> x.getName().compareToIgnoreCase(y.getName()))
            .distinct()
            .forEach(System.out::println);
}
Curso de Java Lambdas con Streams
Te invitamos a ver nuestro curso de Java Lambdas + Streams para aprender a programar con el nuevos paradigma funcional.

Ves lo fácil que es hacer todo esto con los Streams, solo nos preocupamos por lo que queremos hacer pero no por el cómo. Veamos los métodos del Stream utilizados, filter, nos permite filtrar elementos de la lista, para lo cual solo deberemos crear una expresión que regrese un boolean, si regresa true, entonces es agregado al Stream de resultados. Luego tenemos el método sorted() en el cual definimos cual es mayor mediante la comparación de los nombres. Seguido, utilizamos el método distinct() para filtrar los Empleados repetidos, para esto hemos sobreescrito el método equals para comprar por el ID en lugar del Hashcode. Finalmente un foreach para imprimir los resultados:

  • Fernanda Martinez
  • Liliana Castro
  • Oscar Blancarte
  • Rebeca Perez

¡Realmente sorprendente verdad!!, al menos para mí si… Pues bien, así como vimos algunos métodos interesantes de los Streams, debes de saber que la interface Stream proporciona varios métodos interesantes que te pueden ayudar muchísimo, sin embargo la intención de este artículo es introducirte en el concepto de los Stream y que logres entender que son y cómo se utilizan, por lo que si quieres aprender todavía más, poder ver este link, donde explican a profundidad la interface Stream y todos los métodos que expone. Me gustaría explicarte a detalle cada uno de los métodos disponibles, pero en realidad creo que sería reinventar la rueda, pues en el link se explica mucho mejor de lo que yo mismo podría hacerlo.

El resultado se puede apreciar mejor con la siguiente imagen:

Java 8 - Streams

En fin, creo que si lograste entender que son los Streams yo me doy por bien servidor, pero si por algún motivo tienes alguna duda, puedes escribirme en la sección de comentarios y con gusto te contestaré.

10 thoughts to “Java 8 – Streams”

  1. que tal oscar, muy interesante tu explicación y concisa, una pregunta, que sucedería si necesito utilizar un stream dentro de otro, me sucede que estoy haciendo una operacion pero me genera error, la verdad soy nuevo utilizando esto de stream, no sesi me puedas colaborar, muchas gracias, coloco my código.

    List n1 = elementosMenu.stream()
    .filter(m -> m.getNivel() == 1)
    .collect(Collectors.toList());
    n1.forEach(m -> {

    Stream n2 = todosMenu.filter(m1 -> m1.getNivel() == 2 && m1.getIndicePadre() == m.getIndice());

    if (m.getTipo() == 1) { // etiqueta
    Label etiqueta = new Label(m.getTitulo(), ContentMode.HTML);
    etiqueta.setPrimaryStyleName(ValoTheme.MENU_SUBTITLE);
    etiqueta.addStyleName(ValoTheme.LABEL_H4);
    etiqueta.setSizeUndefined();
    disenoElementosMenu.addComponent(etiqueta);

    } else if (m.getTipo() == 2) { // enlace
    Button enlace = new Button(m.getTitulo(), (Button.ClickEvent event) -> {
    Notification.show(“Clic”, “Implementacion de ” + m.getTitulo(), Notification.Type.HUMANIZED_MESSAGE);
    });
    enlace.setCaptionAsHtml(true);
    enlace.setPrimaryStyleName(ValoTheme.MENU_ITEM);
    enlace.setIcon(m.getrIcono(), m.getTitulo());
    enlace.setDescription(m.getTitulo());
    disenoElementosMenu.addComponent(enlace);
    }
    });
    si no coloco el stream interno n2, se ejecuta sin problemas cuando lo coloco me sale el error: java.lang.IllegalStateException: stream has already been operated upon or closed.

    1. Creo que el problema es que no el n2 no puede acceder al elemento m, pues no es una variable final, intenta asignar el valor de m a una variable final y luego, la utilizas dentro del Stream de n2.
      No se si me dí a entender, saludos.

          1. Hola Oscar buenas tardes.

            La siguiente consulta es en relación con que es lo recomendable realizar operaciones de filtrado, suma, group by… A nivel de base de datos o utilizar stream un el desarrollo de una aplicación utilizando java EE8 se entiende que con Jdk 1.8.

            De que depende y cuáles serían tus sugerencias basado en tu experiencia.

            De antemano gracias por tu respuesta.

            Saludos.

          2. Hola Henry,

            Tu pregunta es interesante!, pero antes de responderla, debemos establecer el contexto, los filtros,sum, etc, de la base de datos son para reducir los elementos recuperados de la base de datos y evitar transmitir por la red un gran número de registros innecesarios. Solo imagina que tienes 10 millones de registros, sería una mala idea traértelos de la base de datos para luego filtrarlos en Java.

            Hora bien, lo streams se utiliza para filtrar, agrupar, etc, los registros que ya tenemos en la memoria. Por ejemplo, imagina que tienes una serie de alumnos que están registrados en una clase, y quisieras filtrar todos los alumnos que están en una determinada clase, en tal caso, utilizarías un query que solo te regrese esos alumnos, para reducir el número de registros. Luego imagina que dados esos alumnos que ya recuperaste, quisieras saber cuales sol hombres o mujeres, entonces, como ya están en la memoria, allí puedes usar stream para filtrarlos.

            Respondiendo a tu pregunta, usa la base de datos para filtrar el universo de registros, y usa stream para filtrar los registros una vez que ya están en memoria.

            saludos.

  2. Muchas gracias por tu post, Me gusta mucho tus explicaciones tienes demasiado claro los conceptos y es por eso que es muy fácil entenderte.
    Sigue así, saludos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *