streams multiple fields example collector java java-8 java-stream java-9 collectors

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 o partitioningBy .

  • 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 .