lambdaでListをMapに変換する

List<Beans>をList<Beansの一部>にlambdaを使って一文で変換するの続きとして、Java8で導入されるlambdaを使って簡潔に、List<Beans>からMapに変換する。

Collectors.toMapを使う

Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)でKey, Valueをそれぞれ指定できる。

import static java.util.function.Function.identity;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * List<Beans>をMapにLambdaを使って一行で変換する
 */
public class Main {

    public static void main(String[] args) {
        Main m = new Main();
        
        // DTOを取得する
        List<Dto> dtos = m.getDtos();

        // ListからMapへの変換
        Map<String, Dto> map = dtos.stream()
                .collect(Collectors.toMap(Dto::getKey, identity()));

        // 上のDto::getKey, identity()はdto -> dto.getKey(), dto -> dtoと同じ意味で、次のようにも書ける
        Map<String, Dto> map_ = dtos.stream()
                .collect(Collectors.toMap(dto -> dto.getKey(), dto -> dto));

        // forEachで一つずつ取り出して表示
        map.forEach((key, val) -> System.out.println("key:" + key + " val:" + val));
    }
    
    /**
     * DBからデータを取り出すモック
     * @return DTO
     */
    private List<Dto> getDtos() {
        List<Dto> dtos = Arrays.asList(
                new Dto("key1", "x1")
                , new Dto("key2", "x2")
                );
        return dtos;
    }
}

class Dto {
    public Dto(String key, String x) {
        this.key = key;
        this.x = x;
    }
    String key;
    String x;
    public String getKey() {
        return key;
    }
    public String getX() {
        return x;
    }
    @Override
    public String toString() {
        return "Dto [key=" + key + ", x=" + x + "]";
    }
}

MapのKeyが重複するとき

Listは重複した値を持っている可能性がある。MapのKeyが重複するときの対処方法を考える必要がある。

Collectors.toMapのオーバーロードメソッドを利用する

もしKeyがかぶると、Collectors.toMapでIllegalStateExceptionが起きてしまう。その場合は、オーバロードされたtoMapを使用する。

// 型パラメータ:
// T - 入力要素の型
// K - キー・マッピング関数の出力の型
// U - 値マッピング関数の出力の型
// パラメータ:
// keyMapper - キーを生成するマッピング関数
// valueMapper - 値を生成するマッピング関数
// mergeFunction - 同じキーに関連付けられた値同士の衝突の解決に使用されるマージ関数(Map.merge(Object, Object, BiFunction)に渡される)
Collectors.toMap(Function<? super T, ? extends K> keyMapper,
                 Function<? super T, ? extends U> valueMapper,
                 BinaryOperator<U> mergeFunction)

以下のような使い方になる。

public static void main(String[] args) {
    Main m = new Main();
    
    // DTOを取得する
    List<Dto> dtos = new ArrayList<>();
    // keyが重複するデータを作成する
    dtos.addAll(m.getDtos());
    dtos.addAll(m.getDtos());
    
    // ListからMapへの変換
    Map<String, String> map = dtos.stream()
            .collect(Collectors.toMap(
                    Dto::getKey
                    , Dto::getX
                    , (v1, v2) -> String.join(",", v1, v2));
    // forEachで一つずつ取り出して表示
    map.forEach((key, val) -> System.out.println("key:" + key + " val:" + val));
}

集計関数を利用する

同じことは集計関数を使用しても実現できる。
集計関数を使用するとCollectorsクラスを大量に使うことがあり、staticインポートした方がコードが綺麗になる。

import static java.util.stream.Collectors.*;

public static void main(String[] args) {
// 略

    // ValueがList
    Map<String, List<Dto>> map = dtos.stream()
            .collect(groupingBy(
                    Dto::getKey));
    
    // Valueを独自に計算
    Map<String, String> map_ = dtos.stream()
            .collect(groupingBy(
                    Dto::getKey
                    , mapping(Dto::getX, joining(","))));
}