Java Stream – flatMap (평탄화)

김영한님의 김영한의 실전 자바 – 고급 3편, 람다, 스트림, 함수형 프로그래밍 내용 중 일부로 실무에서 자주 사용될 법한 Stream의 flatMap이다

flatMap

  • map은 각 요소를 하나의 값으로 변환하지만, flatMap은 각 요소를 스트림(또는 여러 요소)으로 변환한 뒤, 그 결과를 하나의 스트림으로 평탄화(flatten)해준다
  • 군대에서 유격 때 A텐트를 치기 전에 울퉁불퉁한 바닥을 평탄화(나라시) 작업 했던 기억이 난다

리스트 안의 리스트

List<List<Integer>> outerList = List.of(
        List.of(1, 2),
        List.of(3, 4),
        List.of(5, 6)
);

System.out.println(outerList);

[
    [1, 2],
    [3, 4],
    [5, 6]
]

// 정확하게는 [[1, 2], [3, 4], [5, 6]] 출력

2중 for문을 통한 평탄화 작업

List<Integer> forResult = new ArrayList<>();

for (List<Integer> list : outerList) {
    for (Integer i : list) {
        forResult.add(i);
    }
}

System.out.println(forResult);
// [1, 2, 3, 4, 5, 6]

map 사용

List<Stream<Integer>> mapResult = outerList.stream() // Stream<List<...>>
    .map(list -> list.stream()) // Stream<Stream<...>>
    .toList();

System.out.println(mapResult);
/*
mapResult =[
    java.util.stream.ReferencePipeline$Head@7291c18f,
    java.util.stream.ReferencePipeline$Head@34a245ab,
    java.util.stream.ReferencePipeline$Head@7cc355be
]
*/

map을 사용하면 이중 구조(List<List<Integer>>)가 그대로 유지된다. 각 요소가 Stream으로 감싸지기 때문에 결과는 List<List<Integer>> 형태가 된다. 또한 mapResult를 직접 출력하면 실제 값이 아니라 Stream 객체의 참조값이 표시되어 [java.util.stream.ReferencePipeline$Head@7291c18f] 같은 형태가 나타난다

  • outerList.stream()
    List<List<Integer>>에서 바깥쪽 리스트가 스트림으로 변환되어 Stream<List<Integer>>가 된다. 즉, 스트림 안에는 3개의 List<Integer> 요소가 들어 있다
  • map(list -> list.stream())
    각 List<Integer>를 Stream<Integer>로 바꾸므로, 결과는 Stream<Stream<Integer>>가 된다. 즉, 바깥 스트림의 요소가 또 다른 스트림인 구조다.
  • toList()
    바깥쪽 Stream<Stream<Integer>>에 대해 실행되므로, 최종 결과는 List<Stream<Integer>>가 된다. 내부에는 3개의 Stream<Integer>가 들어 있으며, 평탄화가 일어나지 않는다.

map은 요소를 1:1로 변환할 뿐 스트림을 병합하지 않기 때문에 평탄화 되지 않고 List<Stream<Integer>> 구조가 유지된다

flatMap 사용

List<Integer> flatMapResult = outerList.stream() // Stream<List<...>>
    .flatMap(list -> list.stream()) // Stream<Integer>
    .toList();

System.out.println(flatMapResult);
// [1, 2, 3, 4, 5, 6]

flatMap을 쓰면 내부의 Stream들을 하나로 합쳐 List<Integer>를 얻을 수 있다

  • outerList.stream()
    List<List<Integer>>에서 바깥쪽 리스트가 스트림으로 변환되어 Stream<List<Integer>>가 된다. 즉, 스트림 안에는 3개의 List<Integer> 요소가 들어 있다
  • flatMap(list -> list.stream())
    각 List<Integer>를 Stream<Integer>로 바꾸는 동시에, 내부 스트림의 요소를 꺼내 외부 스트림에 합친다. 결과적으로 1, 2, 3, 4, 5, 6이 하나의 Stream<Integer>로 이어진다 (map과 달리 중첩 스트림을 병함)
  • toList()
    Stream<Integer>를 수집하여 List<Integer>로 변환한다 최종 결과는 [1, 2, 3, 4, 5, 6] 형태로 반환한다

출처 – 인프런 강의 중 김영한의 실전 자바 – 고급 3편, 람다, 스트림, 함수형 프로그래밍