JavaRush /Blog Java /Random-ES /Pausa para el café #177. Una guía detallada de Java Strea...

Pausa para el café #177. Una guía detallada de Java Stream en Java 8

Publicado en el grupo Random-ES
Fuente: Hackernoon Esta publicación proporciona un tutorial detallado sobre cómo trabajar con Java Stream junto con ejemplos de código y explicaciones. Pausa para el café #177.  Una guía detallada de Java Stream en Java 8 - 1

Introducción a los subprocesos de Java en Java 8

Java Streams, introducido como parte de Java 8, se utiliza para trabajar con colecciones de datos. No son una estructura de datos en sí mismos, pero se pueden utilizar para ingresar información de otras estructuras de datos ordenándola y canalizándola para producir un resultado final. Nota: Es importante no confundir Stream y Thread, ya que en ruso ambos términos a menudo se denominan en la misma traducción "flujo". Stream denota un objeto para realizar operaciones (con mayor frecuencia transferir o almacenar datos), mientras que Thread (traducción literal - hilo) denota un objeto que permite ejecutar cierto código de programa en paralelo con otras ramas de código. Debido a que Stream no es una estructura de datos separada, nunca cambia la fuente de datos. Las secuencias de Java tienen las siguientes características:
  1. Java Stream se puede utilizar utilizando el paquete “java.util.stream”. Se puede importar a un script usando el código:

    import java.util.stream.* ;

    Con este código, también podemos implementar fácilmente varias funciones integradas en Java Stream.

  2. Java Stream puede aceptar entradas de colecciones de datos, como colecciones y matrices en Java.

  3. Java Stream no requiere cambiar la estructura de datos de entrada.

  4. Java Stream no cambia la fuente. En cambio, genera resultados utilizando métodos de canalización apropiados.

  5. Los Java Streams están sujetos a operaciones intermedias y terminales, que analizaremos en las siguientes secciones.

  6. En Java Stream, las operaciones intermedias se canalizan y ocurren en un formato de evaluación diferido. Terminan con funciones terminales. Este forma el formato básico para usar Java Stream.

En la siguiente sección, veremos los diversos métodos utilizados en Java 8 para crear un Java Stream cuando sea necesario.

Crear una secuencia de Java en Java 8

Los subprocesos de Java se pueden crear de varias formas:

1. Crear una secuencia vacía usando el método Stream.empty()

Puede crear una secuencia vacía para usarla más adelante en su código. Si utiliza el método Stream.empty() , se generará una secuencia vacía que no contiene valores. Esta secuencia vacía puede resultar útil si queremos omitir una excepción de puntero nulo en tiempo de ejecución. Para hacer esto puedes usar el siguiente comando:
Stream<String> str = Stream.empty();
La declaración anterior generará una secuencia vacía llamada str sin ningún elemento dentro de ella. Para verificar esto, simplemente verifique el número o tamaño de la transmisión usando el término str.count() . Por ejemplo,
System.out.println(str.count());
Como resultado, obtenemos 0 en la salida .

2. Cree una secuencia utilizando el método Stream.builder() con una instancia de Stream.Builder

También podemos usar Stream Builder para crear una secuencia usando el patrón de diseño del constructor. Está diseñado para la construcción de objetos paso a paso. Veamos cómo podemos crear una instancia de una transmisión usando Stream Builder .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
Con este código, puede crear una secuencia llamada numStream que contenga elementos int . Todo se hace bastante rápido gracias a la instancia de Stream.Builder llamada numBuilder que se crea primero.

3. Cree una secuencia con los valores especificados usando el método Stream.of()

Otra forma de crear una secuencia implica utilizar el método Stream.of() . Esta es una forma sencilla de crear una secuencia con valores determinados. Declara y también inicializa el hilo. Un ejemplo del uso del método Stream.of() para crear una secuencia:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Este código creará una secuencia que contiene elementos int , tal como lo hicimos en el método anterior usando Stream.Builder . Aquí hemos creado directamente una secuencia usando Stream.of() con valores predefinidos [1, 2 y 3] .

4. Cree una secuencia a partir de una matriz existente utilizando el método Arrays.stream()

Otro método común para crear un hilo implica el uso de matrices en Java. La secuencia aquí se crea a partir de una matriz existente utilizando el método Arrays.stream() . Todos los elementos de la matriz se convierten en elementos de secuencia. He aquí un buen ejemplo:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Este código generará un numStream que contiene el contenido de una matriz llamada arr, que es una matriz de números enteros.

5. Fusionar dos transmisiones existentes usando el método Stream.concat()

Otro método que se puede utilizar para crear una secuencia es el método Stream.concat() . Se utiliza para combinar dos hilos para crear un solo hilo. Ambas corrientes se combinan en orden. Esto significa que el primer hilo va primero, seguido del segundo, y así sucesivamente. Un ejemplo de dicha concatenación se ve así:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
La declaración anterior creará una secuencia final llamada CombinedStream que contiene elementos de la primera secuencia numStream1 y de la segunda secuencia numStream2 uno por uno .

Tipos de operaciones con Java Stream

Como ya se mencionó, puede realizar dos tipos de operaciones con Java Stream en Java 8: intermedias y terminales. Veamos cada uno de ellos con más detalle.

Operaciones intermedias

Las operaciones intermedias generan un flujo de salida y se ejecutan solo cuando se encuentran con una operación de terminal. Esto significa que las operaciones intermedias se ejecutan de forma perezosa, canalizadas y solo pueden completarse mediante una operación de terminal. Aprenderá sobre la evaluación diferida y la canalización un poco más adelante. Ejemplos de operaciones intermedias son los siguientes métodos: filter() , map() , different() , peek() , sorted() y algunos otros.

Operaciones terminales

Las operaciones de terminal completan la ejecución de operaciones intermedias y también devuelven los resultados finales del flujo de salida. Debido a que las operaciones de terminal señalan el final de la ejecución diferida y la canalización, este subproceso no se puede volver a utilizar después de haber pasado por una operación de terminal. Ejemplos de operaciones de terminal son los siguientes métodos: forEach() , Collect() , count() , reduce() y así sucesivamente.

Ejemplos de operaciones con Java Stream

Operaciones intermedias

A continuación se muestran algunos ejemplos de algunas operaciones intermedias que se pueden aplicar a un flujo de Java:

filtrar()

Este método se utiliza para filtrar elementos de una secuencia que coinciden con un predicado específico en Java. Estos elementos filtrados forman una nueva secuencia. Echemos un vistazo a un ejemplo para comprenderlo mejor.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Conclusión:
[98]
Explicación: En este ejemplo, puede ver que los elementos pares (divisibles por 2) se filtran utilizando el método filter() y se almacenan en una lista de enteros numStream , cuyo contenido se imprime más adelante. Dado que 98 es el único número entero par en la secuencia, se imprime en la salida.

mapa()

Este método se utiliza para crear una nueva secuencia ejecutando funciones asignadas en elementos de la secuencia de entrada original. Quizás la nueva secuencia tenga un tipo de datos diferente. El ejemplo se ve así:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Conclusión:
[86, 130, 2, 196, 126]
Explicación: Aquí vemos que el método map() se usa para simplemente duplicar cada elemento de la secuencia numStream . Como puede ver en el resultado, cada uno de los elementos de la secuencia se ha duplicado con éxito.

distinto()

Este método se utiliza para recuperar solo elementos individuales en una secuencia filtrando duplicados. Un ejemplo de lo mismo se ve así:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Conclusión:
[43, 65, 1, 98, 63]
Explicación: En este caso, el método diferente() se utiliza para numStream . Recupera todos los elementos individuales en numList eliminando duplicados de la secuencia. Como puede ver en el resultado, no hay duplicados, a diferencia del flujo de entrada, que inicialmente tenía dos duplicados (63 y 1).

ojeada()

Este método se utiliza para rastrear cambios intermedios antes de ejecutar una operación de terminal. Esto significa que peek() se puede utilizar para realizar una operación en cada elemento de una secuencia para crear una secuencia en la que se pueden realizar más operaciones intermedias.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Conclusión:
Mapeado: 430 Mapeado: 650 Mapeado: 10 Mapeado: 980 Mapeado: 630 [430, 650, 10, 980, 630]
Explicación: Aquí el método peek() se utiliza para generar resultados intermedios a medida que el método map() se aplica a los elementos de la secuencia. Aquí podemos notar que incluso antes de usar la operación de terminal Collect() para imprimir el contenido final de la lista en la declaración de impresión , el resultado de cada mapeo de elementos de flujo se imprime secuencialmente por adelantado.

ordenado()

El método sorted() se utiliza para ordenar los elementos de una secuencia. De forma predeterminada, ordena los elementos en orden ascendente. También puede especificar un orden de clasificación específico como parámetro. Un ejemplo de implementación de este método se ve así:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Conclusión:
1 43 ​​​​63 65 98
Explicación: Aquí, el método sorted() se utiliza para ordenar los elementos de la secuencia en orden ascendente de forma predeterminada (ya que no se especifica ningún orden específico). Puede ver que los elementos impresos en el resultado están organizados en orden ascendente.

Operaciones terminales

A continuación se muestran algunos ejemplos de algunas operaciones de terminal que se pueden aplicar a flujos de Java:

para cada()

El método forEach() se utiliza para recorrer todos los elementos de una secuencia y ejecutar la función en cada elemento uno por uno. Esto actúa como una alternativa a declaraciones de bucle como for , while y otras. Ejemplo:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Conclusión:
43 65 1 98 63
Explicación: Aquí el método forEach() se utiliza para imprimir cada elemento de la secuencia uno por uno.

contar()

El método count() se utiliza para recuperar el número total de elementos presentes en la secuencia. Es similar al método size() , que se utiliza a menudo para determinar el número total de elementos de una colección. Un ejemplo del uso del método count() con Java Stream es el siguiente:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Conclusión:
5
Explicación: Dado que numStream contiene 5 elementos enteros, usar el método count() generará 5.

recolectar()

El método Collect() se utiliza para realizar reducciones mutables de elementos de flujo. Se puede utilizar para eliminar contenido de una transmisión una vez completado el procesamiento. Utiliza la clase Collector para realizar reducciones .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Conclusión:
[43, 65, 1, 63]
Explicación: En este ejemplo, todos los elementos impares de la secuencia se filtran y recopilan/reducen en una lista denominada odd . Al final, se imprime una lista de los impares.

mín() y máx()

El método min() , como su nombre indica, se puede utilizar en una secuencia para encontrar el elemento mínimo en ella. De manera similar, el método max() se puede utilizar para encontrar el elemento máximo en una secuencia. Intentemos entender cómo se pueden utilizar con un ejemplo:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Conclusión:
Elemento más pequeño: 1 Elemento más grande: 98
Explicación: En este ejemplo, imprimimos el elemento más pequeño en numStream usando el método min() y el elemento más grande usando el método max() . Tenga en cuenta que aquí, antes de usar el método max() , hemos agregado elementos al numStream nuevamente . Esto se debe a que min() es una operación de terminal y destruye el contenido de la secuencia original, devolviendo solo el resultado final (que en este caso era el número entero "más pequeño").

encontrarAny() y encontrarPrimero()

findAny() devuelve cualquier elemento de la secuencia como Opcional . Si la secuencia está vacía, también devolverá un valor Opcional , que estará vacío. findFirst() devuelve el primer elemento de la secuencia como Opcional . Al igual que con el método findAny() , el método findFirst() también devuelve un parámetro opcional vacío si la secuencia correspondiente está vacía. Echemos un vistazo al siguiente ejemplo basado en estos métodos:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Conclusión:
Opcional[43] Opcional.vacío
Explicación: Aquí, en el primer caso, el método findFirst() devuelve el primer elemento de la secuencia como Opcional . Luego, cuando el hilo se reasigna como un hilo vacío, el método findAny() devuelve un Opcional vacío .

allMatch() , anyMatch() y noneMatch()

El método allMatch() se utiliza para comprobar si todos los elementos de una secuencia coinciden con un determinado predicado y devuelve el valor booleano verdadero si lo hacen; de lo contrario, devuelve falso . Si la secuencia está vacía, devuelve verdadero . El método anyMatch() se utiliza para comprobar si alguno de los elementos de una secuencia coincide con un determinado predicado. Devuelve verdadero si es así, falso en caso contrario. Si la secuencia está vacía, devuelve falso . El método noneMatch() devuelve verdadero si ningún elemento de la secuencia coincide con el predicado y falso en caso contrario. Un ejemplo para ilustrar esto se ve así:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Conclusión:
falso verdadero falso
Explicación: Para una secuencia numStream que contiene 1 como elemento, el método allMatch() devuelve falso porque no todos los elementos son 1, pero solo uno de ellos lo es. El método anyMatch() devuelve verdadero porque al menos uno de los elementos es 1. El método noneMatch() devuelve falso porque 1 realmente existe como elemento en esta secuencia.

Evaluaciones diferidas en Java Stream

La evaluación diferida conduce a optimizaciones cuando se trabaja con Java Streams en Java 8. Implican principalmente retrasar las operaciones intermedias hasta que se encuentra una operación de terminal. La evaluación diferida es responsable de evitar el desperdicio innecesario de recursos en los cálculos hasta que el resultado sea realmente necesario. El flujo de salida resultante de las operaciones intermedias se genera sólo después de que se ha completado la operación terminal. La evaluación diferida funciona con todas las operaciones intermedias en flujos de Java. Un uso muy útil de la evaluación diferida se produce cuando se trabaja con flujos infinitos. De esta manera se evitan muchos procesamientos innecesarios.

Tuberías en Java Stream

Una canalización en un Java Stream consta de un flujo de entrada, cero o más operaciones intermedias alineadas una tras otra y, finalmente, una operación de terminal. Las operaciones intermedias en Java Streams se realizan de forma perezosa. Esto hace inevitables las operaciones intermedias canalizadas. Con las canalizaciones, que son básicamente operaciones intermedias combinadas en orden, se hace posible la ejecución diferida. Las canalizaciones ayudan a realizar un seguimiento de las operaciones intermedias que deben realizarse después de que finalmente se encuentre una operación terminal.

Conclusión

Resumamos ahora lo que hemos aprendido hoy. En este articulo:
  1. Echamos un vistazo rápido a qué son Java Streams.
  2. Luego aprendimos muchas técnicas diferentes para crear subprocesos Java en Java 8.
  3. Aprendimos dos tipos principales de operaciones (operaciones intermedias y operaciones de terminal) que se pueden realizar en flujos de Java.
  4. Luego analizamos en detalle varios ejemplos de operaciones intermedias y terminales.
  5. Terminamos aprendiendo más sobre la evaluación diferida y finalmente aprendiendo sobre la canalización en subprocesos de Java.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION