1. ホーム
  2. java

[解決済み】Kotlinの標準ライブラリで利用できるJava 8 Stream.collectに相当するものは?

2022-04-29 01:24:33

質問

Java 8 では Stream.collect であり、コレクションに対する集約を可能にするものです。 Kotlinでは、これはstdlibの拡張関数のコレクションとして存在する以外には、同じようには存在しない。 しかし、異なるユースケースに対してどのような等価性があるかは明確でない。

例えば のJavaDocのトップ Collectors はJava 8用に書かれた例で、Kolinに移植する場合、異なるJDKバージョンではJava 8のクラスを使用できないので、おそらく別の書き方をする必要があります。

ネットでKotlinのコレクションの例を紹介している資料を見ても、一般的につまらないものばかりで、同じユースケースとは比較にならない。 Java 8のドキュメントにあるような、ケースに本当にマッチした良い例とは何でしょうか? Stream.collect ? そこにリストアップされているのは

  • 名前をListに蓄積する
  • 名前をTreeSetに蓄積する
  • 要素を文字列に変換し、カンマで区切って連結する。
  • 従業員の給与の合計を計算する
  • 部門別グループ分け
  • 部門別給与の合計を計算する
  • 学生を合格と不合格に分ける

詳細は上記リンク先のJavaDocで。

注意 この質問は、意図的に筆者が書き、回答しています( 自分で回答した質問 ) のように、よく聞かれるKotlinのトピックに対するイディオム的な回答がSOに存在するようにします。また、Kotlinのアルファ版として書かれた、現在のKotlinでは正確ではない本当に古い答えを明らかにするためでもあります。

解決方法は?

Kotlinの標準ライブラリには、平均、カウント、区別、フィルタリング、発見、グループ化、結合、マッピング、最小、最大、分割、スライス、ソート、合計、配列との間、リストとの間、マップとの間、ユニオン、協調反復、すべての関数パラダイム、その他の関数が用意されています。 ですから、それらを使って小さな1ライナーを作ることができますし、Java 8のより複雑な構文を使う必要はありません。

私は、ビルトインのJava 8に唯一欠けているのは Collectors クラスは要約です(ただし もう一つの答え が簡単な解決策です) .

両者に欠けているのは、カウントによるバッチ処理で、これは次のようになります。 別のStack Overflowの回答 で、こちらも簡単な回答があります。もう一つ興味深いのは、同じくStack Overflowに掲載されているこのケースです。 Kotlin を使ってシーケンスを 3 つのリストに分割する直感的な方法 . また、次のようなものを作りたい場合 Stream.collect を参照してください。 KotlinでカスタムStream.collect

2017.08.11を編集しました。 kotlin 1.2 M2 で Chunked/window コレクション操作が追加されたので、以下を参照。 https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/


を探求することは常に良いことです。 kotlin.collectionsのAPIリファレンス は、すでに存在する可能性のある関数を新たに作成する前に、全体として作成する必要があります。

以下は、Java 8 からの変換です。 Stream.collect の例からKotlinの同等の例へ。

名前をリストに蓄積する

// Java:  
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

// Kotlin:
val list = people.map { it.name }  // toList() not needed

要素を文字列に変換し、カンマで区切って連結します。

// Java:
String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));

// Kotlin:
val joined = things.joinToString(", ")

従業員の給与の合計を計算する

// Java:
int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));

// Kotlin:
val total = employees.sumBy { it.salary }

部署ごとに社員をグループ化

// Java:
Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

// Kotlin:
val byDept = employees.groupBy { it.department }

部門別給与の合計を計算する

// Java:
Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                     Collectors.summingInt(Employee::getSalary)));

// Kotlin:
val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

合格と不合格に生徒を仕切る

// Java:
Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));

// Kotlin:
val passingFailing = students.partition { it.grade >= PASS_THRESHOLD }

男性メンバーの名前

// Java:
List<String> namesOfMaleMembers = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());

// Kotlin:
val namesOfMaleMembers = roster.filter { it.gender == Person.Sex.MALE }.map { it.name }

名簿に登録されているメンバーの名前を性別でグループ分けする

// Java:
Map<Person.Sex, List<String>> namesByGender =
      roster.stream().collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.mapping(
                Person::getName,
                Collectors.toList())));

// Kotlin:
val namesByGender = roster.groupBy { it.gender }.mapValues { it.value.map { it.name } }   

リストから別のリストへのフィルタリング

// Java:
List<String> filtered = items.stream()
    .filter( item -> item.startsWith("o") )
    .collect(Collectors.toList());

// Kotlin:
val filtered = items.filter { it.startsWith('o') } 

リストの最短文字列の検索

// Java:
String shortest = items.stream()
    .min(Comparator.comparing(item -> item.length()))
    .get();

// Kotlin:
val shortest = items.minBy { it.length }

フィルタを適用した後のリスト内の項目を数える

// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();

// Kotlin:
val count = items.filter { it.startsWith('t') }.size
// but better to not filter, but count with a predicate
val count = items.count { it.startsWith('t') }

といった具合です。 すべての場合において、特別なfoldやreduceなどの機能は必要ありません。 Stream.collect . もし、さらに詳しい使用例があれば、コメントで追加してください。

怠け心について

チェーンの処理を遅延させたい場合は、そのチェーンを Sequence を使って asSequence() をチェーンの前に置く。 関数の連鎖の最後には、通常は Sequence と同じです。 そうすると toList() , toSet() , toMap() を実体化させるための何らかの関数が必要です。 Sequence を最後に追加します。

// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()

なぜTypeがないのか!?

Kotlinの例では、型を指定していないことにお気づきでしょう。 これは、Kotlinが完全な型推論を持ち、コンパイル時に完全に型安全であるためです。 Javaよりもさらに、NULL可能な型もあり、恐ろしいNPEを防ぐのに役立つからです。 だからKotlinでこれ。

val someList = people.filter { it.age <= 30 }.map { it.name }

と同じです。

val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }

なぜならKotlinは people があり、その people.ageInt したがって、このフィルタ式では Int であり、その people.nameString したがって map ステップでは List<String> (読み取り専用 ListString ).

さて、もし people はもしかしたら null というように List<People>? では

val someList = people?.filter { it.age <= 30 }?.map { it.name }

を返します。 List<String>? を使用すると、NULLチェックが必要になります ( または、Kotlin の他の演算子を使って null 可能な値を指定することもできます。 Kotlin の慣用的な null 値の扱い方 また Kotlin で null 可能なリストまたは空のリストを処理する慣例的な方法 )

こちらもご覧ください。