添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
有腹肌的啄木鸟  ·  使用 JdbcTemplate ...·  1 周前    · 
爱跑步的钥匙  ·  MybatisPlus学习笔记 | ...·  1 周前    · 
咆哮的馒头  ·  Get Nth Entry from ...·  1 周前    · 
留胡子的汤圆  ·  SharePoint 搜索 REST ...·  6 天前    · 
没有腹肌的开水瓶  ·  Exception in thread ...·  6 天前    · 
温暖的梨子  ·  「Top 15 ...·  2 周前    · 
小胡子的日光灯  ·  JumpCloud SSO | ...·  4 月前    · 
斯文的大象  ·  android - How to set ...·  1 年前    · 

当サイトは、アフィリエイト広告を利用しています

【java8】streamのgroupingbyの使い方

作成日:2022月04月10日
更新日:2024年02月08日

StreamAPIのgroupingByとは?

Collectors.groupingByを使うと配列やListをグルーピングし、
Map<K,List< T >> のMap型データを取得できる
※Kは集約キー、Tは集約対象のオブジェクト。

似たようなことができる「partitioningby」メソッドもある
partitioningbyメソッドについては下記記事で紹介しています。

下記の本では streamAPI についても詳しく書いてありおススメです!

また当ブログで紹介しているjava8のstreamAPIを使った
コレクションや配列の操作方法を
下記記事でメソッド別にまとめています!

基本構文

Collectors.groupingByは引数の数が1~3つの場合がある。

引数が1つの場合(デフォルト)

java
Map<K,List< T >> groupingBy(Function<T,K> classifier)
  • 第一引数は集約キーを返す関数を関数型インターフェースのFunction型
  • Tはオブジェクト。
  • Kは集約キー。基本的にはTの属性のどれかになる。

引数が2つの場合

java
Map<K,D> groupingBy(Function<T,K> classifier,Collector<? super T,A,D> downstream)
  • 第一引数は集約キーを返す関数を関数型インターフェースのFunction型
  • Kは集約キー。基本的にはTの属性のどれかになる。
  • 第二引数は集約キーで集約した結果を指定の型に変換する関数等をセットする
  • Dは第二引数で変換した結果を同じ型になる

ややこしいが
引数一つの場合は戻り値は Map<K,List< T >> 固定だが
第二引数の関数で変換してやることで

  • Map<K,Set< T >>
  • Map<K,Map< K,V >>

など、集約キーで集約した結果の型を変えることができる。

引数が3つの場合

java
M extends Map<K,D> groupingBy(Function<T,K> classifier, Supplier<M> mapFactory,Collector<? super T,A,D> downstream)
  • 第一引数は集約キーを返す関数を関数型インターフェースのFunction型
  • Kは集約キー。基本的にはTの属性のどれかになる。
  • 第二引数は最終結果として取得したいMapの実装クラス(treeMapやLinkedHashMap)を指定する
  • 第三引数は集約キーで集約した結果を指定の型に変換する関数等をセットする
  • Dは第二引数で変換した結果を同じ型になる

最終結果をtreeMapやLinkedHashMapで受け取りた時に使う

groupingByを使ってサンプル実装してみる

groupingByでは色々できることがあるのでサンプル実装していく

集約対象クラス

Personクラスを使う

Person.java
public class Person {
private final int id;
private final String name;
private final String address;
Person(int id,String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getAddress() {
return this.address;
@Override
public String toString()
{
return this.id + " " + this.name + " " + this.address;
}
}

デフォルト

groupingByに引数を一つだけ設定した場合
その引数を集約キーにした Map<K,List< T >> で値を取得できる。

単一項目のグルーピング

集約キーが一つのパターン

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","仙台"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","岐阜"));
// グルーピング
Map<Integer, List<Person>> map =
personlist.stream().collect(
Collectors.groupingBy(
Person::getId //集約キー
// t->t.getId() //メソッド参照を利用しないver
)
);
System.out.println(map);
}
}
// 実行結果
// {
// 1=[
// {1 TANAKA 東京},
// {1 YOSHIDA 青森}
// ],
// 2=[
// {2 YAMADA 山梨}
// ],
// 3=[
// {3 OOTANI 熊本},
// {3 TUJIMURA 京都},
// {3 MURATA 仙台}
// ],
// 4=[
// {4 OKAMOTO 大阪},
// {4 MORITA 滋賀},
// {4 KAWASAKI 岐阜}
// ]
// }

集約キーにしたidをキーにしてPersonオブジェクトがグルーピングされる

複数項目のグルーピング

集約キーを複数にした場合

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
Map<String, List<Person>> map =
personlist.stream().collect(
Collectors.groupingBy(
t->t.getId() + t.getAddress() //複合キーを作る
)
);
System.out.println(map);
}
}
// 実行結果
// {
// 1東京=[
// {1 TANAKA 東京}
// ],
// 2山梨=[
// {2 YAMADA 山梨}
// ],
// 4滋賀=[
// {4 MORITA 滋賀},
// {4 KAWASAKI 滋賀}
// ],
// 4大阪=[
// {4 OKAMOTO 大阪}
// ],
// 3熊本=[
// {3 OOTANI 熊本},
// {3 MURATA 熊本}
// ],
// 3京都=[
// {3 TUJIMURA 京都}
// ],
// 1青森=[
// {1 YOSHIDA 青森}
// ]
// }

複合キーにしたidとaddressをキーにしてPersonオブジェクトがグルーピングされる

集約結果の型を変えて受け取る

引数を2つにして集約結果をList< T >以外の型で受け取る

toMap

Map<K,List< T >>のList< T >の部分をMapで受け取る

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
// 集約結果をMapで取得する
Map<Integer, Map<Integer,String>> map =
personlist.stream().collect(
Collectors.groupingBy(
Person::getId,
Collectors.toMap( //集約結果のMapを作成
e->e.getId(), // キーはId
e->e.getName(), // バリューはname
(oldVal,newVal) -> newVal) // 後勝ち
)
);
System.out.println(map);
}
}
// 実行結果
// {
// 1={1=YOSHIDA},
// 2={2=YAMADA},
// 3={3=MURATA},
// 4={4=KAWASAKI}
// }

集約結果がMapで取得できている。
Mapにキーが重複しているため後勝ちになっている

toSet

Map<K,List< T >>のList< T >の部分をSetで受け取る

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
// 集約結果をSetで取得する
Map<Integer, Set<Person>> map =
personlist.stream().collect(
Collectors.groupingBy(
Person::getId,
Collectors.toSet() //Setにする
)
);
System.out.println(map);
}
}
// 実行結果
// {
// 1=[
// {1 TANAKA 東京}, {1 YOSHIDA 青森}
// ],
// 2=[
// {2 YAMADA 山梨}
// ],
// 3=[
// {3 TUJIMURA 京都}, {3 MURATA 熊本}, {3 OOTANI 熊本}
// ],
// 4=[
// {4 MORITA 滋賀}, {4 KAWASAKI 滋賀}, {4 OKAMOTO 大阪}
// ]
// }

Setで取得できる

mapping

Map<K,List< T >>のList< T >の部分を任意のListで受け取る
このサンプルではPerson.addressのListで受け取るようにする。

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
// 集約結果をPerson.addressのListで取得
Map<Integer, List<String>> nameByPerson
= personlist.stream().collect(
Collectors.groupingBy(
Person::getId,
Collectors.mapping( //集約結果をさらにmappingする
Person::getAddress, //集約キーごとにaddressをまとめる
Collectors.toList() //まとめたaddressをListにする
)
)
);
System.out.println(nameByPerson);
}
}
// 実行結果
// {
// 1=[
// 東京, 青森
// ],
// 2=[
// 山梨
// ],
// 3=[
// 熊本, 京都, 熊本
// ],
// 4=[
// 大阪, 滋賀, 滋賀
// ]
// }

集約キーごとにadrressのListが取得できる。

Mapクラスを指定して受け取る

groupingByに引数を3つ設定して
その引数を集約キーにした Map<K,List< T >> のMapを
任意のMapクラスで取得する

Collectors.mappingを使う

Collectors.mappingを使って戻り値のMapを
LinkedHashMapにしてみる

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
LinkedHashMap<Integer, List<Person>> nameByPerson
= personlist.stream().collect(
Collectors.groupingBy(
Person::getId,
LinkedHashMap::new, //LinkedHashMapで取得
Collectors.mapping(p->p,Collectors.toList())) //mappingを使う
);
System.out.println(nameByPerson);
System.out.println(nameByPerson instanceof LinkedHashMap);
// 実行結果
// {
// 1=[1 TANAKA 東京, 1 YOSHIDA 青森],
// 2=[2 YAMADA 山梨],
// 3=[3 OOTANI 熊本, 3 TUJIMURA 京都, 3 MURATA 熊本],
// 4=[4 OKAMOTO 大阪, 4 MORITA 滋賀, 4 KAWASAKI 滋賀]
// }
// true

任意のListにする

mappingを使って任意のListにすることもできる

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
// 集約結果をPerson.addressのListで取得
LinkedHashMap<Integer, List<String>> nameByPerson
= personlist.stream().collect(
Collectors.groupingBy(
Person::getId,
LinkedHashMap::new, //LinkedHashMapで取得
Collectors.mapping(
Person::getAddress,
Collectors.toList()
)
)
);
System.out.println(nameByPerson);
}
}
// 実行結果
// {
// 1=[
// 東京, 青森
// ],
// 2=[
// 山梨
// ],
// 3=[
// 京都, 熊本, 熊本
// ],
// 4=[
// 滋賀, 滋賀, 大阪
// ]
// }

集約いろいろ

他にもgroupingBy後に編集できるCollectorsのサンプルをのせる

グループごとに合計する

id毎にグルーピングしてからidごとのidの合計を出す

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京"),
new Person(1,"YOSHIDA","青森"),
new Person(2,"YAMADA","山梨"),
new Person(3,"OOTANI","熊本"),
new Person(3,"TUJIMURA","京都"),
new Person(3,"MURATA","熊本"),
new Person(4,"OKAMOTO","大阪"),
new Person(4,"MORITA","滋賀"),
new Person(4,"KAWASAKI","滋賀"));
// グルーピング
// 集約結果をPerson.addressのListで取得
Map<Integer, Integer> nameByPerson
= personlist.stream()
.collect(
Collectors.groupingBy(
Person::getId,
Collectors.reducing(
0,//初期値
(value)->value.getId(),//mappingする
(accum, value)-> accum + value //計算
)
)
);
System.out.println(nameByPerson);
}
}
// 実行結果
// {1=2, 2=2, 3=9, 4=12}

mappingのところでMap< Integer, Integer >のInteger型に変換してやる必要がある

グループごとに最大値を出す

Personクラス

ageを追加する

Person.java
public class Person {
private final int id;
private final String name;
private final String address;
Person(int id,String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getAddress() {
return this.address;
@Override
public String toString()
{
return this.id + " " + this.name + " " + this.address;
}
}

id毎にグルーピングしてからidごとのid内で最大のageのオブジェクトを取得する

Main.java
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// PersonのListを作成
List<Person> personlist = Arrays.asList(
new Person(1,"TANAKA","東京",11),
new Person(1,"YOSHIDA","青森",21),
new Person(2,"YAMADA","山梨",23),
new Person(3,"OOTANI","熊本",45),
new Person(3,"TUJIMURA","京都",80),
new Person(3,"MURATA","熊本",22),
new Person(4,"OKAMOTO","大阪",2),
new Person(4,"MORITA","滋賀",54),
new Person(4,"KAWASAKI","滋賀",78));
// グルーピング
// 集約結果をPerson.addressのListで取得
Map<Integer, Optional<Person>> nameByPerson
= personlist.stream()
.collect(
Collectors.groupingBy(
Person::getId,//idで集約
Collectors.maxBy(Comparator.comparing(Person::getAge))//各グループでageが最大値を取得
)
);
System.out.println(nameByPerson);
}
}
// 実行結果
// {
// 1=Optional[{1 YOSHIDA 青森21}],
// 2=Optional[{2 YAMADA 山梨23}],
// 3=Optional[{3 TUJIMURA 京都80}],
// 4=Optional[{4 KAWASAKI 滋賀78}]
// }
  • maxByは戻り値はOptionalになる

まとめ

処理の流れイメージとしては

  • grupingByで集約キーごとのListを作成する。
  • Map<K,List< T >> の形で結果が生成される(デフォルトの形)
  • Map< K, List < T > >の部分を色々編集したり型変えたりして返却する。

のような感じだと思う。
一旦は Map<K,List< T >> の形になると考えれば処理がイメージしやすい。

参考

下記を参考させていただきました
- 【Stream API】groupingBy と mapping でリストをマッピングしつつグルーピングする

参考書籍

下記の本ではstreamAPIについても詳しく書いてあり、おススメです!