API de transmisión
Existen varias formas de procesar datos en Java: bucles, métodos y diferentes algoritmos. Sin embargo, en Java 8 se introdujo una herramienta muy potente: la API Stream.
En términos sencillos, la API Stream es una forma de trabajar rápida y fácilmente con un flujo de información. En nuestro caso, este flujo de información está representado por colecciones. El Stream API tiene algunos conceptos. Éstos son los principales.
Conceptos principales
-
Flujo:** Representa una secuencia de elementos de datos que pueden ser procesados.
-
Operaciones intermedias: Operaciones que crean un nuevo flujo tras su ejecución. Ejemplos:
filter
,map
,distinct
,sorted
. -
Operaciones Terminales: Operaciones que completan el procesamiento del flujo y devuelven un resultado. Ejemplos:
collect
,forEach
,count
,reduce
. -
Flujos paralelos**: Permiten el procesamiento paralelo de datos. Los métodos
parallel()
yparallelStream()
se utilizan para crear flujos paralelos.
Basta de hablar de teoría, ¡empecemos a codificar!
Declarar un stream se hace usando un método en la colección que queremos convertir en stream:
main.java
12List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream();
Con el método stream()
, obtenemos un flujo de cadenas. Pero para empezar a trabajar con el stream, necesitamos entender qué son las expresiones lambda, ya que los métodos stream trabajan principalmente con ellas.
Expresiones lambda
Las expresiones lambda se introdujeron en Java 8, y representan una forma simplificada de crear funciones anónimas en Java. No hemos cubierto funciones anónimas antes ya que no eran muy necesarias, pero ahora nos familiarizaremos con ellas a través de las expresiones lambda.
Sintaxis de la expresión lambda:
La sintaxis general de las expresiones lambda en Java es la siguiente:
example.java
123(parameters) -> expression // or (parameters) -> { statements; }
-
Parámetros:** Es una lista de parámetros que puede estar vacía o contener uno o más parámetros.
-
Flecha: Representada por el símbolo
->
, que separa los parámetros del cuerpo de la expresión lambda. -
Expresión o sentencias: Es el cuerpo de la función, que contiene una expresión o un bloque de sentencias.
He aquí un ejemplo de expresión lambda que representa una función simple que añade dos números:
example.java
12345678910// Traditional way MathOperation addition = new MathOperation() { @Override public int operate(int a, int b) { return a + b; } }; // Using a lambda expression MathOperation addition = (int a, int b) -> a + b;
Veamos con más detalle qué ocurre exactamente en el código anterior y cómo utilizamos las expresiones lambda:
main.java
1234567891011121314package com.example; // Functional interface with a single abstract method interface MyMathOperation { int operate(int a, int b); } public class Main { public static void main(String[] args) { // Using a lambda expression to implement the interface MyMathOperation addition = (a, b) -> a + b; System.out.println("Sum: " + addition.operate(5, 3)); } }
Explicación:
- Creada una interfaz funcional
MyMathOperation
con un único método abstractooperate
. - Utilizado una expresión lambda para implementar este método, realizando la suma de dos números.
- Imprime el resultado de la suma.
Entiendo que puede ser difícil de entender lo que está pasando en este código por ahora, pero volvamos a la Stream API, donde las expresiones lambda se utilizan con frecuencia, y tratemos de entender cómo utilizarlas en la práctica.
Como recordarás, antes creamos un stream de cadenas a partir de una lista de cadenas. Ahora, vamos a utilizar métodos de flujo para hacer que cada cadena de este flujo sea uppercase:
main.java
12345678910111213package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); stream.map(e -> e.toUpperCase()).toList(); } }
En el código anterior, usamos una expresión lambda y dos métodos: map()
y toList()
. Si está claro lo que hace el método toList()
, el método map()
cambia cada elemento del flujo según la expresión lambda proporcionada.
Veamos más de cerca cómo funciona la expresión lambda:
El método map()
aplica el método toUpperCase()
a cada elemento del flujo. Hemos definido el elemento de este flujo como e
y, usando la expresión lambda, hemos ordenado al programa que aplique este método a cada elemento.
Pero aquí no acaba la cosa, porque hemos aplicado una operación intermedia. Esto significa que las operaciones sobre el flujo aún no se han completado. Para completar el trabajo sobre el flujo, necesitamos aplicar una operación terminal, que terminará las operaciones sobre el flujo y devolverá un valor específico. Por ejemplo, podemos usar el método toList()
, y el flujo modificado será convertido en una lista.
Por ejemplo:
main.java
1234567891011121314package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.toUpperCase()).toList(); System.out.println(list); } }
Nota
Nota que después de usar la operación terminal, ya no podemos usar métodos de flujo. En nuestro caso, después de la operación terminal
toList()
, nuestro flujo fue convertido en una lista, por lo que no podemos usar métodos de flujo en la lista.
Echemos un vistazo más de cerca a las posibles operaciones intermedias en el flujo.
Operaciones intermedias
- El método
map()
- ya estás familiarizado con este método; realiza las operaciones especificadas por la expresión lambda en cada elemento del flujo.
Por ejemplo, usemos el método substring()
en cada elemento del flujo de cadenas:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
- El método
filter()
toma una expresión lambda con una condición basada en la cual el flujo será filtrado. En otras palabras, todos los elementos que cumplan la condición permanecerán en el flujo, y los elementos que no cumplan la condición serán eliminados del flujo. Modifiquemos el flujo para mantener sólo los elementos cuya longitud sea superior a 5:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); stream = stream.filter(e -> e.length() > 5); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
Usando el método filter()
, eliminamos la cadena "con" del flujo porque esta palabra tiene menos de 5 caracteres.
También puedes utilizar operaciones intermedias varias veces seguidas.
Por ejemplo, podemos simplificar ligeramente el código anterior:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); List<String> list = strings.stream() .filter(e -> e.length() > 5) .map(e -> e.substring(1, 4)) .toList(); System.out.println("Modified list: " + list); } }
Al encadenar múltiples métodos de flujo, se recomienda poner cada método en una nueva línea para mejorar significativamente la legibilidad del código.
- El método
flatMap()
transforma cada elemento de un flujo en un nuevo flujo y combina los resultados en un único flujo. En otras palabras, con este método, podemos dividir el flujo en flujos, y luego se combinarán en un solo flujo. Por ejemplo, tenemos una lista de cadenas donde cada cadena puede contener más de una palabra, como una lista de nombres y apellidos. Y necesitamos capitalizar la primera letra de cada una de estas palabras:
main.java
123456789101112131415161718192021222324package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
En el código anterior, escribimos un método privado independiente que pone en mayúsculas la primera letra de una palabra y utilizamos este método en el método map()
junto con una expresión lambda.
Observa que usando el método flatMap
, dividimos cada elemento del flujo en diferentes flujos usando el método Arrays.stream(e.split(" "))
. Como el método split()
devuelve un array, necesitamos usar el método Arrays.stream()
para dividir este array en streams.
Después, todos estos flujos se fusionan en un flujo, tras lo cual usamos el método que hemos escrito. Voilà, ahora tenemos todos los nombres y apellidos de los usuarios con la primera letra en mayúscula.
**¿Sabes lo que sería genial?
Si ponemos estos nombres y apellidos en un HashMap
, donde la clave es el apellido y el valor es el nombre.
Implementemos esto en el código:
main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); Map<String, String> usersKeyValue = new HashMap<>(); for (int i = 0; i < list.size() - 1; i+=2) { String name = list.get(i); String surname = list.get(i + 1); usersKeyValue.put(surname, name); } System.out.println("Map with surnames as keys and names as values: " + usersKeyValue); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
Con un bucle simple, almacenamos el nombre y el apellido en variables y luego en el mapa. Observa cómo funciona el bucle. Incrementamos la variable i
en 2 en cada iteración porque necesitamos saltar el apellido una vez que ya lo hemos registrado.
Nota
Este método de filtrado de datos es bastante arriesgado porque los datos se pueden registrar en el orden incorrecto, pero en nuestro caso, no juega un papel importante.
- El método
distinct()
elimina duplicados del flujo. En general, esto puede ser útil si necesitas elementos únicos en el flujo o si quieres eliminar rápidamente duplicados de una lista. Puedes conseguirlo fácilmente con la siguiente construcción:
list.stream().distinct().toList()
-
El método
sorted
ordena todos los elementos del flujo en orden natural, del menor al mayor número o en orden alfabético. Esto también puede ser útil si necesitas un flujo ordenado o si necesitas ordenar rápidamente una lista. -
El método
skip(n)
omite los primerosn
elementos del flujo. Esto puede ser útil cuando se trabaja con archivos de texto, donde las primeras n líneas pueden ser, por ejemplo, metadatos o la descripción de un archivo. También vale la pena mencionar el métodolimit(n)
, que generalmente limita el número de elementos en el flujo. Incluso si creamos un flujo con 1000 elementos y luego usamoslimit(200)
, el flujo contendrá sólo los primeros 200 elementos.
main.java
123456789101112package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example = example.stream().skip(3).limit(5).toList(); System.out.println("List: " + example); } }
Estos son los principales métodos intermedios que necesitarás utilizar. Puedes explorar el resto de métodos consultando la página enlace a la documentación oficial de Java. Pasemos a los métodos de terminal.
Métodos terminales
-
El método terminal con el que ya estás familiarizado es
toList()
. Este método convierte el flujo en una lista y la devuelve. En otras palabras, podemos asignar directamente este flujo con métodos a una lista. Este método se introdujo en Java 17 y sirve como sustituto de la construcción más complejacollect(Collectors.toList())
. -
El método
collect()
también convierte el flujo en una estructura de datos específica. Utiliza como parámetro un método de la interfazCollectors
. Esta interfaz tiene métodos comotoList()
,toSet()
, ytoCollection()
. Por ejemplo:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Set<Integer> integerSet = example.stream().collect(Collectors.toSet()); System.out.println("List: " + example); System.out.println("Set: " + integerSet); } }
El método forEach()
toma una expresión lambda y realiza una acción específica para cada elemento del flujo.
Por ejemplo:
main.java
1234567891011package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example.stream().forEach(e -> System.out.println(e + 1)); } }
La diferencia entre este método y el método map
es que este método es terminal, y después de él, no puedes llamar a otros métodos.
Todos estos son los métodos básicos para trabajar con flujos. Es un tema complejo, y puede que no lo entiendas inmediatamente. Sin embargo, es un tema que se domina con la práctica. En los próximos capítulos de práctica con streams, tendrás muchas oportunidades de trabajar con ellos, ¡ya que es una forma muy conveniente y práctica de manipular listas y arrays de datos!
1. ¿Cuál es el objetivo principal de la API Stream en Java?
2. ¿Cuál de las siguientes es una operación terminal en la Stream API?
3. ¿Qué hace la operación map
en la Stream API?
4. ¿En qué se diferencia la operación flatMap
de map
en la Stream API?
5. ¿Qué hace la operación filter
en la Stream API?
6. ¿Cuál es el propósito de la operación forEach
en la API Stream?
7. ¿Cuál de las siguientes es una operación intermedia en la Stream API?
8. ¿Cómo se utiliza la operación limit
en la Stream API?
¡Gracias por tus comentarios!
Pregunte a AI
Pregunte a AI
Pregunte lo que quiera o pruebe una de las preguntas sugeridas para comenzar nuestra charla
Awesome!
Completion rate improved to 4
API de transmisión
Desliza para mostrar el menú
Existen varias formas de procesar datos en Java: bucles, métodos y diferentes algoritmos. Sin embargo, en Java 8 se introdujo una herramienta muy potente: la API Stream.
En términos sencillos, la API Stream es una forma de trabajar rápida y fácilmente con un flujo de información. En nuestro caso, este flujo de información está representado por colecciones. El Stream API tiene algunos conceptos. Éstos son los principales.
Conceptos principales
-
Flujo:** Representa una secuencia de elementos de datos que pueden ser procesados.
-
Operaciones intermedias: Operaciones que crean un nuevo flujo tras su ejecución. Ejemplos:
filter
,map
,distinct
,sorted
. -
Operaciones Terminales: Operaciones que completan el procesamiento del flujo y devuelven un resultado. Ejemplos:
collect
,forEach
,count
,reduce
. -
Flujos paralelos**: Permiten el procesamiento paralelo de datos. Los métodos
parallel()
yparallelStream()
se utilizan para crear flujos paralelos.
Basta de hablar de teoría, ¡empecemos a codificar!
Declarar un stream se hace usando un método en la colección que queremos convertir en stream:
main.java
12List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream();
Con el método stream()
, obtenemos un flujo de cadenas. Pero para empezar a trabajar con el stream, necesitamos entender qué son las expresiones lambda, ya que los métodos stream trabajan principalmente con ellas.
Expresiones lambda
Las expresiones lambda se introdujeron en Java 8, y representan una forma simplificada de crear funciones anónimas en Java. No hemos cubierto funciones anónimas antes ya que no eran muy necesarias, pero ahora nos familiarizaremos con ellas a través de las expresiones lambda.
Sintaxis de la expresión lambda:
La sintaxis general de las expresiones lambda en Java es la siguiente:
example.java
123(parameters) -> expression // or (parameters) -> { statements; }
-
Parámetros:** Es una lista de parámetros que puede estar vacía o contener uno o más parámetros.
-
Flecha: Representada por el símbolo
->
, que separa los parámetros del cuerpo de la expresión lambda. -
Expresión o sentencias: Es el cuerpo de la función, que contiene una expresión o un bloque de sentencias.
He aquí un ejemplo de expresión lambda que representa una función simple que añade dos números:
example.java
12345678910// Traditional way MathOperation addition = new MathOperation() { @Override public int operate(int a, int b) { return a + b; } }; // Using a lambda expression MathOperation addition = (int a, int b) -> a + b;
Veamos con más detalle qué ocurre exactamente en el código anterior y cómo utilizamos las expresiones lambda:
main.java
1234567891011121314package com.example; // Functional interface with a single abstract method interface MyMathOperation { int operate(int a, int b); } public class Main { public static void main(String[] args) { // Using a lambda expression to implement the interface MyMathOperation addition = (a, b) -> a + b; System.out.println("Sum: " + addition.operate(5, 3)); } }
Explicación:
- Creada una interfaz funcional
MyMathOperation
con un único método abstractooperate
. - Utilizado una expresión lambda para implementar este método, realizando la suma de dos números.
- Imprime el resultado de la suma.
Entiendo que puede ser difícil de entender lo que está pasando en este código por ahora, pero volvamos a la Stream API, donde las expresiones lambda se utilizan con frecuencia, y tratemos de entender cómo utilizarlas en la práctica.
Como recordarás, antes creamos un stream de cadenas a partir de una lista de cadenas. Ahora, vamos a utilizar métodos de flujo para hacer que cada cadena de este flujo sea uppercase:
main.java
12345678910111213package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); stream.map(e -> e.toUpperCase()).toList(); } }
En el código anterior, usamos una expresión lambda y dos métodos: map()
y toList()
. Si está claro lo que hace el método toList()
, el método map()
cambia cada elemento del flujo según la expresión lambda proporcionada.
Veamos más de cerca cómo funciona la expresión lambda:
El método map()
aplica el método toUpperCase()
a cada elemento del flujo. Hemos definido el elemento de este flujo como e
y, usando la expresión lambda, hemos ordenado al programa que aplique este método a cada elemento.
Pero aquí no acaba la cosa, porque hemos aplicado una operación intermedia. Esto significa que las operaciones sobre el flujo aún no se han completado. Para completar el trabajo sobre el flujo, necesitamos aplicar una operación terminal, que terminará las operaciones sobre el flujo y devolverá un valor específico. Por ejemplo, podemos usar el método toList()
, y el flujo modificado será convertido en una lista.
Por ejemplo:
main.java
1234567891011121314package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.toUpperCase()).toList(); System.out.println(list); } }
Nota
Nota que después de usar la operación terminal, ya no podemos usar métodos de flujo. En nuestro caso, después de la operación terminal
toList()
, nuestro flujo fue convertido en una lista, por lo que no podemos usar métodos de flujo en la lista.
Echemos un vistazo más de cerca a las posibles operaciones intermedias en el flujo.
Operaciones intermedias
- El método
map()
- ya estás familiarizado con este método; realiza las operaciones especificadas por la expresión lambda en cada elemento del flujo.
Por ejemplo, usemos el método substring()
en cada elemento del flujo de cadenas:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
- El método
filter()
toma una expresión lambda con una condición basada en la cual el flujo será filtrado. En otras palabras, todos los elementos que cumplan la condición permanecerán en el flujo, y los elementos que no cumplan la condición serán eliminados del flujo. Modifiquemos el flujo para mantener sólo los elementos cuya longitud sea superior a 5:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); stream = stream.filter(e -> e.length() > 5); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
Usando el método filter()
, eliminamos la cadena "con" del flujo porque esta palabra tiene menos de 5 caracteres.
También puedes utilizar operaciones intermedias varias veces seguidas.
Por ejemplo, podemos simplificar ligeramente el código anterior:
main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); List<String> list = strings.stream() .filter(e -> e.length() > 5) .map(e -> e.substring(1, 4)) .toList(); System.out.println("Modified list: " + list); } }
Al encadenar múltiples métodos de flujo, se recomienda poner cada método en una nueva línea para mejorar significativamente la legibilidad del código.
- El método
flatMap()
transforma cada elemento de un flujo en un nuevo flujo y combina los resultados en un único flujo. En otras palabras, con este método, podemos dividir el flujo en flujos, y luego se combinarán en un solo flujo. Por ejemplo, tenemos una lista de cadenas donde cada cadena puede contener más de una palabra, como una lista de nombres y apellidos. Y necesitamos capitalizar la primera letra de cada una de estas palabras:
main.java
123456789101112131415161718192021222324package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
En el código anterior, escribimos un método privado independiente que pone en mayúsculas la primera letra de una palabra y utilizamos este método en el método map()
junto con una expresión lambda.
Observa que usando el método flatMap
, dividimos cada elemento del flujo en diferentes flujos usando el método Arrays.stream(e.split(" "))
. Como el método split()
devuelve un array, necesitamos usar el método Arrays.stream()
para dividir este array en streams.
Después, todos estos flujos se fusionan en un flujo, tras lo cual usamos el método que hemos escrito. Voilà, ahora tenemos todos los nombres y apellidos de los usuarios con la primera letra en mayúscula.
**¿Sabes lo que sería genial?
Si ponemos estos nombres y apellidos en un HashMap
, donde la clave es el apellido y el valor es el nombre.
Implementemos esto en el código:
main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); Map<String, String> usersKeyValue = new HashMap<>(); for (int i = 0; i < list.size() - 1; i+=2) { String name = list.get(i); String surname = list.get(i + 1); usersKeyValue.put(surname, name); } System.out.println("Map with surnames as keys and names as values: " + usersKeyValue); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
Con un bucle simple, almacenamos el nombre y el apellido en variables y luego en el mapa. Observa cómo funciona el bucle. Incrementamos la variable i
en 2 en cada iteración porque necesitamos saltar el apellido una vez que ya lo hemos registrado.
Nota
Este método de filtrado de datos es bastante arriesgado porque los datos se pueden registrar en el orden incorrecto, pero en nuestro caso, no juega un papel importante.
- El método
distinct()
elimina duplicados del flujo. En general, esto puede ser útil si necesitas elementos únicos en el flujo o si quieres eliminar rápidamente duplicados de una lista. Puedes conseguirlo fácilmente con la siguiente construcción:
list.stream().distinct().toList()
-
El método
sorted
ordena todos los elementos del flujo en orden natural, del menor al mayor número o en orden alfabético. Esto también puede ser útil si necesitas un flujo ordenado o si necesitas ordenar rápidamente una lista. -
El método
skip(n)
omite los primerosn
elementos del flujo. Esto puede ser útil cuando se trabaja con archivos de texto, donde las primeras n líneas pueden ser, por ejemplo, metadatos o la descripción de un archivo. También vale la pena mencionar el métodolimit(n)
, que generalmente limita el número de elementos en el flujo. Incluso si creamos un flujo con 1000 elementos y luego usamoslimit(200)
, el flujo contendrá sólo los primeros 200 elementos.
main.java
123456789101112package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example = example.stream().skip(3).limit(5).toList(); System.out.println("List: " + example); } }
Estos son los principales métodos intermedios que necesitarás utilizar. Puedes explorar el resto de métodos consultando la página enlace a la documentación oficial de Java. Pasemos a los métodos de terminal.
Métodos terminales
-
El método terminal con el que ya estás familiarizado es
toList()
. Este método convierte el flujo en una lista y la devuelve. En otras palabras, podemos asignar directamente este flujo con métodos a una lista. Este método se introdujo en Java 17 y sirve como sustituto de la construcción más complejacollect(Collectors.toList())
. -
El método
collect()
también convierte el flujo en una estructura de datos específica. Utiliza como parámetro un método de la interfazCollectors
. Esta interfaz tiene métodos comotoList()
,toSet()
, ytoCollection()
. Por ejemplo:
main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Set<Integer> integerSet = example.stream().collect(Collectors.toSet()); System.out.println("List: " + example); System.out.println("Set: " + integerSet); } }
El método forEach()
toma una expresión lambda y realiza una acción específica para cada elemento del flujo.
Por ejemplo:
main.java
1234567891011package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example.stream().forEach(e -> System.out.println(e + 1)); } }
La diferencia entre este método y el método map
es que este método es terminal, y después de él, no puedes llamar a otros métodos.
Todos estos son los métodos básicos para trabajar con flujos. Es un tema complejo, y puede que no lo entiendas inmediatamente. Sin embargo, es un tema que se domina con la práctica. En los próximos capítulos de práctica con streams, tendrás muchas oportunidades de trabajar con ellos, ¡ya que es una forma muy conveniente y práctica de manipular listas y arrays de datos!
1. ¿Cuál es el objetivo principal de la API Stream en Java?
2. ¿Cuál de las siguientes es una operación terminal en la Stream API?
3. ¿Qué hace la operación map
en la Stream API?
4. ¿En qué se diferencia la operación flatMap
de map
en la Stream API?
5. ¿Qué hace la operación filter
en la Stream API?
6. ¿Cuál es el propósito de la operación forEach
en la API Stream?
7. ¿Cuál de las siguientes es una operación intermedia en la Stream API?
8. ¿Cómo se utiliza la operación limit
en la Stream API?
¡Gracias por tus comentarios!