web-dev-qa-db-ja.com

JavaストリームでgroupByにフィルタリングを適用する方法

最初にグループ化し、次に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]]}

どのようにこれを行うことができますか?

21
Niraj Sonawane

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

16
Holger

このために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()操作とは異なります。

21
Naman

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());
2
Niraj Sonawane