Los streams de Java se introdujeron en Java 8 como parte de la API de Streams 🔗, proporcionando una forma moderna y funcional de procesar colecciones de datos. Los Streams permiten realizar operaciones de manera declarativa y paralela, mejorando la legibilidad y el rendimiento del código.
Hoy en día prácticamente su uso se ha convertido en un must para cualquier profesional que se dedique a la programación.
Algunas ventajas de utilizar Java Streams 👀💥:
Código más conciso y legible
Las operaciones con Streams son más compactas y fáciles de leer.
Paralelismo
Los Streams permiten ejecutar operaciones en paralelo de manera sencilla.
Inmutabilidad
Los Streams no modifican las colecciones originales, lo que ayuda a evitar errores.
Composición de operaciones
Los Streams permiten encadenar múltiples operaciones de manera fluida.
Mejor rendimiento
Las operaciones con Streams pueden ser más eficientes gracias a optimizaciones internas.
Comencemos por lo super básico... el inicio del source
List<Employee> employees = List.of(
new Employee("Alice", 30, 70000, "Company A", List.of("Java", "Spring", "SQL")),
new Employee("Bob", 25, 60000, "Company B", List.of("JavaScript", "React", "Node.js")),
new Employee("Charlie", 35, 50000, "Company C", List.of("Java", "Spring", "SQL"))
);
Source 🔥
Operaciones intermedias 🔌
map
//Map
List<String> employeeNames =
employees.stream()
.map((s) -> s.name().toUpperCase())
.collect(Collectors.toList());
System.out.println(employeeNames);
Output:
ALICE
BOB
CHARLIE
flatMap
// flatMap
Set<String> uniqueSkills = employees.stream()
.flatMap(employee -> employee.skills().stream())
.collect(Collectors.toSet());
System.out.println(uniqueSkills);
Output:
[Java, JavaScript, Node.js, Spring, React, SQL]
filter
// Filter
List<String> employeeFiltered = employees.stream()
.filter((e) -> e.age()> 30)
.map (Employee::name)
.collect(Collectors.toList());
System.out.println(employeeFiltered);
Output:
sort
// Sort
List>Employee> sortedEmployees = employees.stream()
.sorted(Comparator.comparingInt(Employee::age))
.collect(Collectors.toList());
System.out.println(sortedEmployees);
Output:
// Sort
sortedEmployees = employees.stream()
.sorted(Comparator.comparingDouble(Employee::salary).reversed())
.collect(Collectors.toList());
System.out.println(sortedEmployees);
limit
// limit
List<Employee> limitedEmployees = employees.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(skipEmployees);
skip
// skip
List<Employee> skipEmployees = employees.stream()
.skip(1)
.collect(Collectors.toList());
System.out.println(skipEmployees);
Output:
[Employee[name=Bob, age=25, salary=60000.0, company=Company B], Employee[name=Charlie, age=35, salary=50000.0, company=Company C]]
Operaciones finales 📕
foreach
void forEach(Consumer<? super T> action)
List<Employee> employees = List.of(
new Employee("Alice", 30, 70000, "Company A"),
new Employee("Bob", 25, 60000, "Company B"),
new Employee("Charlie", 35, 50000, "Company C")
);
employees.stream()
.map((s) -> s.name().toUpperCase())
.forEach(System.out::println);
Output:
[ALICE, BOB, CHARLIE]
count
// count
long adulltsCount = employees.stream()
.map(Employee::age)
.filter(a -> a > 18)
.count();
System.out.println(adulltsCount);
Output:
3
allMatch
Nos dice si todos los elementos del stream cumplen un predicado que se pasa como parámetro. Por ejemplo, ¿todos los empleados cobran más de 60000?
// allMatch
boolean allMatch = employees.stream()
.allMatch(e -> e.salary()> 60000);
System.out.println("Are all the employees rich : " + allMatch);
Output:
Are all the employees rich : false
noneMatch
Nos dice si ningún elemento del stream cumplen un predicado que se pasa como parámetro. Por ejemplo, ¿ningún empleado es mayor a 65 años?
// noneMatch
boolean noneMatch = employees.stream()
.noneMatch(e -> e.age() > 65);
System.out.println("Are all the employees below 65 : " + noneMatch);
Output:
Are all the employees below 65 : true
min
Nos devuelve el minimo elemento del stream en base a un criterio de comparación. En este caso le pesamos el criterio de edad, por lo que devolveré el empleado con menor edad.
// Min
Optional<Employee> minAgeEmpOpt = employees.stream()
.min(Comparator.comparing(Employee::age));
Employee minAgeEmp = minAgeEmpOpt.get();
System.out.println("Employee with minimum age is : " + minAgeEmp);
max
Nos devuelve el máximo elemento del stream en base a un criterio de comparación. En este caso le pesamos el criterio de edad, por lo que devolveré el empleado con mayor edad.
// Max
Optional<Employee> maxAgeEmpOpt = employees.stream()
.max(Comparator.comparing(Employee::age));
Employee maxAgeEmp = maxAgeEmpOpt.get();
System.out.println("Employee with maximum age is : " + maxAgeEmp);
Output:
Collectors
- Collectors.groupingBy()
- Collectors.averagingInt()
- Collector.toList()
- Collectors.toSet()
- Collectors.toMap()
// Collectors
List<String> collectorList =
employees.stream()
.map(Employee::name)
.collect(Collectors.toList());
System.out.println(collectorList);
[Alice, Bob, Charlie]
En este otro ejemplo obtenemos un Map con el nombre de la compañia junto con la lista de empleados que la compone:
List<Employee> allEmployees = List.of(
new Employee("Alice", 30, 70000, "Company A", List.of("Java", "Spring", "SQL")),
new Employee("Bob", 25, 60000, "Company B", List.of("JavaScript", "React", "Node.js")),
new Employee("Charlie", 35, 50000, "Company C", List.of("Java", "Spring", "SQL")),
new Employee("David", 28, 55000, "Company A", List.of("Java", "Spring", "SQL"))
);
// Group employees by company
Map<String, List<Employee>> employeesByCompany = allEmployees.stream()
.collect(Collectors.groupingBy(Employee::company));
System.out.println(employeesByCompany);
Output:
{Company A=[Employee[name=Alice, age=30, salary=70000.0, company=Company A, skills=[Java, Spring, SQL]], Employee[name=David, age=28, salary=55000.0, company=Company A, skills=[Java, Spring, SQL]]], Company B=[Employee[name=Bob, age=25, salary=60000.0, company=Company B, skills=[JavaScript, React, Node.js]]], Company C=[Employee[name=Charlie, age=35, salary=50000.0, company=Company C, skills=[Java, Spring, SQL]]]}
¿Y si juntamos todo?
El peek lo utilizo para hacer debug y revisar qué esta pasando en nuestras expresiones, aunque normalmente es una operación que no suele subir a entornos productivos. 👀👮.
// peek, reduce y parallel
Double salary = employees.stream()
.peek(System.out::println)
.map(Employee::salary)
.reduce(0.0, Double::sum);
System.out.println("Sum of all salaries: " + salary);
Double salaryParallel = employees.stream().parallel()
.peek(System.out::println)
.map(Employee::salary)
.reduce(0.0, Double::sum);
System.out.println("Sum of all salaries parallel way: " + salaryParallel);
Employee[name=Alice, age=30, salary=70000.0, company=Company A, skills=[Java, Spring, SQL]]
Employee[name=Bob, age=25, salary=60000.0, company=Company B, skills=[JavaScript, React, Node.js]]
Employee[name=Charlie, age=35, salary=50000.0, company=Company C, skills=[Java, Spring, SQL]]
Sum of all salaries: 180000.0
Employee[name=Bob, age=25, salary=60000.0, company=Company B, skills=[JavaScript, React, Node.js]]
Employee[name=Charlie, age=35, salary=50000.0, company=Company C, skills=[Java, Spring, SQL]]
Employee[name=Alice, age=30, salary=70000.0, company=Company A, skills=[Java, Spring, SQL]]
Sum of all salaries parallel way: 180000.0
Comentarios
Publicar un comentario