java - multiple - stream join
Cómo aplicar el filtrado en groupBy en java streams (4)
¿Cómo agrupas primero y luego aplicas el filtrado usando flujos de Java?
Ejemplo : considere esta clase de Employee
: quiero agrupar por departamento con una lista de un empleado que tenga un salario superior a 2000.
public class Employee {
private String department;
private Integer salary;
private String name;
//getter and setter
public Employee(String department, Integer salary, String name) {
this.department = department;
this.salary = salary;
this.name = name;
}
}
Así es como puedo hacer esto.
List<Employee> list = new ArrayList<>();
list.add(new Employee("A", 5000, "A1"));
list.add(new Employee("B", 1000, "B1"));
list.add(new Employee("C", 6000, "C1"));
list.add(new Employee("C", 7000, "C2"));
Map<String, List<Employee>> collect = list.stream()
.filter(e -> e.getSalary() > 2000)
.collect(Collectors.groupingBy(Employee::getDepartment));
Salida
{A=[Employee [department=A, salary=5000, name=A1]],
C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}
Como no hay empleados en el Departamento B con un salario superior a 2000. Entonces, no hay una clave para el Departamento B: pero en realidad, quiero tener esa llave con la lista vacía -
Rendimiento esperado
{A=[Employee [department=A, salary=5000, name=A1]],
B=[],
C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}
¿Cómo podemos hacer esto?
Puede hacer uso de la API de Collectors.filtering
introducida en Java-9 para esto:
Map<String, List<Employee>> output = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.filtering(e -> e.getSalary() > 2000, Collectors.toList())));
Importante de la nota de la API :
Los recopiladores filtering () son más útiles cuando se utilizan en una reducción de varios niveles, como el flujo descendente de una
groupingBy
opartitioningBy
.Un colector de filtrado difiere de la operación de
filter()
de un flujo.
Use el Map#putIfAbsent(K,V)
para llenar los huecos después del filtrado
Map<String, List<Employee>> map = list.stream()
.filter(e->e.getSalary() > 2000)
.collect(Collectors.groupingBy(Employee::getDepartment, HashMap::new, toList()));
list.forEach(e->map.putIfAbsent(e.getDepartment(), Collections.emptyList()));
Nota: Dado que no se garantiza que el mapa devuelto por groupingBy sea mutable, debe especificar un Proveedor de mapas para estar seguro (gracias a shmosel por señalarlo).
Otra solución (no recomendada) es usar toMap
lugar de groupingBy
, que tiene el inconveniente de crear una lista temporal para cada empleado. También se ve un poco desordenado.
Predicate<Employee> filter = e -> e.salary > 2000;
Map<String, List<Employee>> collect = list.stream().collect(
Collectors.toMap(
e-> e.department,
e-> new ArrayList<Employee>(filter.test(e) ? Collections.singleton(e) : Collections.<Employee>emptyList()) ,
(l1, l2)-> {l1.addAll(l2); return l1;}
)
);
No hay una forma más limpia de hacer esto en Java 8: ha mostrado un enfoque claro en java8 here Aceptó la respuesta.
Así es como lo he hecho en java 8:
Paso: 1 Grupo por departamento
Paso: 2 coloca en bucle cada elemento y comprueba si el departamento tiene un empleado con salario> 2000
Paso: 3 actualiza los valores de copia del mapa en el nuevo mapa basado en noneMatch
Map<String, List<Employee>> employeeMap = list.stream().collect(Collectors.groupingBy(Employee::getDepartment));
Map<String, List<Employee>> newMap = new HashMap<String,List<Employee>>();
employeeMap.forEach((k, v) -> {
if (v.stream().noneMatch(emp -> emp.getSalary() > 2000)) {
newMap.put(k, new ArrayList<>());
}else{
newMap.put(k, v);
}
});
Java 9: coleccionistas. Filtrado
Java 9 ha agregado nuevos recopiladores de Collectors.filtering
. Filtrado de este grupo primero y luego aplica el filtrado. El colector de filtrado está diseñado para ser utilizado junto con la agrupación .
Los colectores. El filtrado toma una función para filtrar los elementos de entrada y un colector para recolectar los elementos filtrados:
list.stream().collect(Collectors.groupingBy(Employee::getDepartment),
Collectors.filtering(e->e.getSalary()>2000,toList());
La respuesta de nullpointer muestra el camino directo a seguir. Si no puede actualizar a Java 9, no hay problema, este colector de filtering
no es magia. Aquí hay una versión compatible con Java 8:
public static <T, A, R> Collector<T, ?, R> filtering(
Predicate<? super T> predicate, Collector<? super T, A, R> downstream) {
BiConsumer<A, ? super T> accumulator = downstream.accumulator();
return Collector.of(downstream.supplier(),
(r, t) -> { if(predicate.test(t)) accumulator.accept(r, t); },
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}
Puede agregarlo a su base de código y usarlo de la misma manera que su contraparte de Java 9, por lo que no tiene que cambiar el código de ninguna manera, si está usando import static
.