1. ホーム
  2. Java

Java8 FlatMapを使ったStreamによるListのマージ

2022-02-27 09:55:40

以前からJava8を使った記事をたくさん書いてきましたが、振り返ってみるとJava8 StreamのflatMap操作を紹介していなかったように思います。昨日、たまたま仕事中にある場面に遭遇し、flatMapがあまりにも便利であることに気づきました。ちなみに、Java8を利用して並列・交差・差分演算のコレクションを実現する方法ですが、実は以前、Guavaの実装を利用する方法も話題になりましたので、具体的にご紹介しますと グアバコレクションツール

  • フラットマップ

まず、Map<Integer, ListContainer> が存在し、ListContainerにList<AClass>のメンバー変数があるシナリオを見てください。Mapの中のList<AClass>の値をすべてList<AClass>に結合する必要があります。このようにすることができるかもしれません。

List<AClass> resultAClassList = Lists.newArrayList();
for (ListContainer tmp : map.values()){
    resultAClassList.addAll(tmp.getLst());
}

これは、Listの階層が1つしかない場合はまだしも、複数の階層がある場合は、やはりforをネストする必要があり、使い勝手が悪いです。そこで、Java8 StreamのflatMapメソッド定義から、このシナリオにはflatMap操作が有効であることを知りました。

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

引数はTからStramへの変換を提供するFunction関数インターフェースである。実際、メソッドの実装を見ると、Functionから変換されたStramをStreamにマージするのがflatMapです。ここでは、上記と同じ機能を実現する使用例を見てみましょう。

@Test
public void mergeMapValuesTest(){
    Map<Integer, ListContainer> map = Maps.newHashMap();
    List<AClass> aClassList1 = Lists.newArrayList();
    AClass aClass = new AClass(1, "zhuoli1", "haha1");
    aClassList1.add(aClass);
    aClassList1.add(new AClass(2, "zhuoli2", "haha2"));
    aClassList1.add(new AClass(3, "zhuoli3", "haha3"));

    List<AClass> aClassList2 = Lists.newArrayList();
    aClassList2.add(aClass);
    aClassList2.add(new AClass(5, "zhuoli5", "haha5"));
    aClassList2.add(new AClass(6, "zhuoli6", "haha6"));

    /*intersection*/
    /*[AClass(id=1, name=zhuoli1, description=haha1)]*/
    List<AClass> intersectResult = aClassList1.stream().filter(aClassList2::contains).collect(Collectors.toList());
    System.out.println(intersectResult);

    /*merge*/
    List<AClass> unionResult = Stream.of(aClassList1, aClassList2).flatMap(Collection::stream).distingu().collect(Collectors.toList( ));
    assertEquals(unionResult.size(), 5);
    System.out.println(unionResult);

    /*difference set*/
    /*[AClass(id=2, name=zhuoli2, description=haha2), AClass(id=3, name=zhuoli3, description=haha3)]*/
    List<AClass> differenceResult = aClassList1.stream().filter(x -> !aClassList2.contains(x)).collect(Collectors.toList());
    System.out.println(differenceResult);

    map.put(1, new ListContainer(aClassList1));
    map.put(2, new ListContainer(aClassList2));

    /*Merge multiple lists*/
    List<AClass> aClassListResult = map.values().stream().flatMap(listContainer -> listContainer.getLst().stream()).collect( Collectors.toList());
    /*Note the difference with merge*/
    assertEquals(aClassListResult.size(), 6);
    System.out.println(aClassListResult);
}

List<Data1>とList<Data2>を実装したflatMapの複合操作を共有して、Idに基づいて結合し、結合の結果をList<OutputData>として出力してください。

@Data
@AllArgsConstructor
public class Data1 {
    private int id;
    private String name;
    private int amount;
}

@Data
@AllArgsConstructor
public class Data2 {
    private int id;
    private String name;
    private String type;
}

@Data
@AllArgsConstructor
public class OutputData {
    private int id;
    private String name;
    private String type;
    private int amount;
}


@Test
public void intersectByKeyTest(){
    List<Data2> listOfData2 = new ArrayList<Data2>();

    listOfData2.add(new Data2(10501, "JOE" , "Type1"));
    listOfData2.add(new Data2(10603, "SAL" , "Type5"));
    listOfData2.add(new Data2(40514, "PETER", "Type4"));
    listOfData2.add(new Data2(59562, "JIM" , "Type2"));
    listOfData2.add(new Data2(29415, "BOB" , "Type1"));
    listOfData2.add(new Data2(61812, "JOE" , "Type9"));
    listOfData2.add(new Data2(98432, "JOE" , "Type7"));
    listOfData2.add(new Data2(62556, "JEFF" , "Type1"));
    listOfData2.add(new Data2(10599, "TOM" , "Type4"));


    List<Data1> listOfData1 = new ArrayList<Data1>();

    listOfData1.add(new Data1(10501, "JOE" ,3000000));
    listOfData1.add(new Data1(10603, "SAL" ,6225000));
    listOfData1.add(new Data1(40514, "PETER" ,2005000));
    listOfData1.add(new Data1(59562, "JIM" ,3000000));
    listOfData1.add(new Data1(29415, "BOB" ,3000000));

    List<OutputData> result = listOfData1.stream()
            .flatMap(x -> listOfData2.stream()
                    .filter(y -> x.getId() == y.getId())
                    .map(y -> new OutputData(y.getId(), x.getName(), y.getType(), x.getAmount()))))
            .collect(Collectors.toList());
    System.out.println(result);

    /*difference by key*/
    List<Data1> data1IntersectResult = listOfData1.stream().filter(data1 -> listOfData2.stream().map(Data2::getId).collect( Collectors.toList()).contains(data1.getId())).collect(Collectors.toList());
    System.out.println(data1IntersectResult);
}


  • faltMapToInt

まず、flatMapToIntメソッドの定義を見てください。

IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);

flatMapとは異なり,パラメータFunction機能インタフェースは,TからIntStreamへの変換を提供し,メソッドはIntStreamの値を返す.

@Test
public void flatMapToIntTest() {
    List<List<String>> listOfLists = Arrays.asList(
            Arrays.asList("1", "2"),
            Arrays.asList("5", "6"),
            Arrays.asList("3", "4")
    );

    IntStream intStream =
            listOfLists.stream()
                    .flatMapToInt(childList ->
                            childList.stream()
                                    .mapToInt(Integer::new));

    int sum = intStream.peek(System.out::println).sum();
    System.out.println("sum: " + sum);
}

Stream インターフェースには flatMapToDouble と flatMapToLong という同様のメソッドもあり、これらは flatMapToInt と同じように使用されるので、ここでは説明せず、メソッドの定義だけを列挙しておくことにします。

DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);

サンプルコードです。 Code Cloud - Joly - Java8 flatMap の例