最初にグループ化し、次にJavaストリームを使用してフィルタリングを適用しますか?
例:このEmployee
クラスを考慮してください:給与が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;
}
}
これは私がこれを行う方法です
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));
出力
{A=[Employee [department=A, salary=5000, name=A1]],
C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}
部門Bには2000年を超える給与を持つ従業員はいないため、部門Bにはキーがありません。実際には、空のリストでそのキーを持ちたい–
期待される出力
{A=[Employee [department=A, salary=5000, name=A1]],
B=[],
C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}
どのようにこれを行うことができますか?
nullpointer's answer は、簡単な方法を示しています。 Java 9に更新できない場合は問題ありません。このfiltering
コレクターは魔法ではありません。 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]));
}
コードベースに追加して、Java 9の対応するものと同じように使用できます。したがって、import static
。
このためにJava-9で導入された_Collectors.filtering
_APIを使用できます。
_Map<String, List<Employee>> output = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.filtering(e -> e.getSalary() > 2000, Collectors.toList())));
_
重要なAPIノートから:
Filtering()コレクターは、
groupingBy
またはpartitioningBy
のダウンストリームなどのマルチレベルリダクションで使用する場合に最も役立ちます。フィルタリングコレクターは、ストリームの
filter()
操作とは異なります。
Map#putIfAbsent(K,V)
を使用して、フィルタリング後にギャップを埋めます
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()));
注:groupingByによって返されるマップは変更可能であることが保証されていないため、確認するためにMap Supplierを指定する必要があります(それを指摘してくれたshmoselに感謝します)。
別の(推奨されない)ソリューションは、toMap
の代わりに groupingBy
を使用することです。これには、すべての従業員の一時リストを作成するという欠点があります。また、少し乱雑に見えます
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;}
)
);
Java 8:でこれを行うよりクリーンな方法はありません。Holger はJava8で明確なアプローチを示しました here 回答を受け入れました。
これは、Java 8:
ステップ:1部門ごとのグループ
ステップ:2ループは各要素をスローし、部門に給与> 2000の従業員がいるかどうかを確認します
ステップ:3 地図を更新する 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:Collectors.filtering
Java 9は新しいコレクターを追加しましたCollectors.filtering
このグループは最初にフィルタリングを適用します。 フィルタリングコレクターは、groupingとともに使用するように設計されています。
Collectors.Filteringは、入力要素をフィルタリングする機能と、フィルタリングされた要素を収集するコレクターを取ります。
list.stream().collect(Collectors.groupingBy(Employee::getDepartment),
Collectors.filtering(e->e.getSalary()>2000,toList());