Java StreamのcollectがSQLのgroupBy的なことができる件

# collect : グルーピング
Java8で追加されたStreamのcollectがSQLのgroupBy的なことが出来ることをしってびっくりした件

# サンプルで使用するクラス
```java
class MyClass {
  private String param1;
  private int param2;

  public MyClass(String param1, int param2) {
    this.param1 = param1;
    this.param2 = param2;
  }
  
  public String getParam1() {
    return param1;
  }

  public int getParam2() {
    return param2;
  }
}
```

```java
class Prefecture {
  private int id;
  private String name;
  private List<City> cities;

  public Prefecture(int id, String name,  List<City> cities) {
    this.id = id;
    this.name = name;
    this.cities = cities;
  }
  
  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public List<City> getCities() {
    return cities;
  }

}
```
```java
class City {
  private int id;
  private String name;
  private int population;

  public City(CityEntity cityEntity) {
    this.id = cityEntity.getId();
    this.name = cityEntity.getName();
    this.population = cityEntity.getPopulation();
  }
  
  public City(int id, String name, int population) {
    this.id = id;
    this.name = name;
    this.population = population;
  }
  
  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public int getPopulation() {
    return population;
  }

  @Override
  public String toString() {
    return "id:" + id + ", name:" + name + ", population:" + population;
  }
}
```



## グルーピング

### param1の値で要素をグルーピング

```java
List<MyClass> array = new ArrayList<MyClass>();
array.add(new MyClass("A", 1));
array.add(new MyClass("A", 2));
array.add(new MyClass("A", 3));
array.add(new MyClass("B", 4));
array.add(new MyClass("B", 5));
array.add(new MyClass("C", 6));

// param1の値でグルーピング
Map<String, List<MyClass>> elementByPrama1 = array.stream()
.collect(Collectors.groupingBy(MyClass::getParam1));

for(Map.Entry<String, List<MyClass>> element : elementByPrama1.entrySet()) {
    System.out.println("param1: " + element.getKey());
    
    // グリープされた子リスト出力
    for (MyClass childElement : element.getValue()) {
      System.out.println("  " + "param2: " + childElement.getParam2());
    }
}
```

```console
param1: A
  param2: 1
  param2: 2
  param2: 3
param1: B
  param2: 4
  param2: 5
param1: C
  param2: 6
```

## グループごとに集計

### param1毎に要素の数集計

```java
List<MyClass> array = new ArrayList<MyClass>();
array.add(new MyClass("A", 1));
array.add(new MyClass("A", 2));
array.add(new MyClass("A", 3));
array.add(new MyClass("B", 4));
array.add(new MyClass("B", 5));
array.add(new MyClass("C", 6));

// param1毎に要素の数を集計
Map<String, Long> elementByPrama1 = array.stream()
.collect(Collectors.groupingBy(MyClass::getParam1,Collectors.counting()));

for(Map.Entry<String, Long> element : elementByPrama1.entrySet()) {
    System.out.println("param1: " + element.getKey() + " ,count:" + element.getValue());
}
```

```console
param1: A ,count:3
param1: B ,count:2
param1: C ,count:1
```

### param1毎にparam2の値を集計

```java
List<MyClass> array = new ArrayList<MyClass>();
array.add(new MyClass("A", 1));
array.add(new MyClass("A", 2));
array.add(new MyClass("A", 3));
array.add(new MyClass("B", 4));
array.add(new MyClass("B", 5));
array.add(new MyClass("C", 6));

// param1毎にparam2の値を集計
Map<String, Integer> elementByPrama1 = array.stream()
.collect(Collectors.groupingBy(MyClass::getParam1,Collectors.summingInt(MyClass::getParam2)));

for(Map.Entry<String, Integer> element : elementByPrama1.entrySet()) {
    System.out.println("param1: " + element.getKey() + " ,totalParam2:" + element.getValue());
}
```

```console
param1: A ,totalParam2:6
param1: B ,totalParam2:9
param1: C ,totalParam2:6
```

### 注意

Mapを宣言する時、合計値の型はintとかのプリミティブ型ではなく、Integerなどのオブジェクト型を使用すること。理由は「Collectors.summingInt」の関数の定義がそうだから！

×
```
Map<String, int> elementByPrama1 = ...
```

〇
```
Map<String, Integer> elementByPrama1 = ...
```

## グループごとに集計（集計処理カスタマイズ）

マイナス値は集計しない。

```java
List<MyClass> array = new ArrayList<MyClass>();
array.add(new MyClass("A", -1));
array.add(new MyClass("A", 2));
array.add(new MyClass("A", 3));
array.add(new MyClass("B", -4));
array.add(new MyClass("B", 5));
array.add(new MyClass("C", 6));

// param1毎にparam2の値を集計
Map<String, Integer> elementByPrama1 = array.stream()
.collect(Collectors.groupingBy(MyClass::getParam1,Collectors.summingInt(e -> e.getParam2() > 0 ? e.getParam2() : 0)));

for(Map.Entry<String, Integer> element : elementByPrama1.entrySet()) {
    System.out.println("param1: " + element.getKey() + " ,totalParam2:" + element.getValue());
}
```

```console
param1: A ,totalParam2:5
param1: B ,totalParam2:5
param1: C ,totalParam2:6
```

## ネストしている配列の集計

```
[
  {東京, [{新宿区, 100}, {渋谷区, 200}]},
  {神奈川, [{横浜, 300}, {川崎, 400}]},
]
```
を
```
[
  {東京, 300},
  {神奈川, 700},
]
```
にするイメージ。

```java
List<Prefecture> prefectures = new ArrayList<Prefecture>();

prefectures.add(new Prefecture(1, "東京", Arrays.asList(
  new City(11, "新宿区", 100),
  new City(12, "渋谷区", 200))
  ));
  
prefectures.add(new Prefecture(2, "神奈川", Arrays.asList(
  new City(21, "横浜", 300), 
  new City(22, "川崎", 400))
));

// 都道府県IDごとに人口取得
Map<Integer, Integer> populationByPrefecure = prefectures.stream()
.collect(Collectors.groupingBy(Prefecture::getId, 
  Collectors.summingInt(p -> p.getCities().stream().collect(Collectors.summingInt(City::getPopulation))
)));

// populationByPrefecure => {1=300, 2=700}
```