Map이란?

map은 스트림 내부의 요소 하나하나에 접근해서 제가 파라미터로 넣어준 함수를 실행한 뒤 최종연산에서 지정한 형식으로 반환해주는 메서드 입니다.

   List<Person> people = Arrays.asList(
                new Person("personA", 24),
                new Person("personB", 26),
                new Person("personC", 28),
                new Person("personD", 30)
        );

위와 같은 리스트를 예시로 만들어보겠습니다.

List<String> nameList = people.stream()
                .map(person -> person.getName())
                .collect(Collectors.toList());

map 메서드 내부를 보면 람다 표현식이 인자로 들어간 것을 알 수 있습니다.

람다 표현식은

  1. people 리스트의 Person 객체 하나하나에 접근한다.
  2. getName 메서드를 통해 name값을 얻는다.
  3. name값들을 포함하는 list를 최종적으로 만들어서 반환한다.

라는 과정을 나타냅니다.

스트림을 쓰지 않을때는 for문을 사용해서 요소 하나하나에 접근해야 했지만, map이 반복문의 역할을 대신해주는 것입니다.

nameList = [personA, personB, personC, personD]

결과를 출력했을때 정상적으로 name값들로 만들어진 리스트가 반환된 것을 알 수 있습니다.

flatMap이란?

map에 대해서는 간략하게 알아봤는데, flatMap은 어떤 기능을 가지고 있을까요? flatMap은 스트림 평면화라는 이름으로도 불립니다. flatMap은 예시를 먼저 보는것이 이해하기 쉽기 때문에 아래의 코드를 한번 보겠습니다.

animal = ["cat","dog"]

위의 리스트에 스트림을 적용하여 cat과 dog의 철자 하나하나를 분리해서 새로운 리스트로 만드는 작업을 진행해보겠습니다.

원하는 결과  = [ "c", "a", "t", "d", "o", "g" ]

먼저 map을 사용해서 철자들을 분리해보겠습니다.

List<String[]> results = animals.stream().map(animal -> animal.split(""))
                                .collect(Collectors.toList());

분명 split 메서드를 이용하여 철자를 모두 분리했는데 반환값 타입을 보면 List<String[]>이 나온것을 알 수 있습니다.

[  [  "c", "a", "t"  ] , [ "d", "o", "g" ]  ]

즉, 위의 그림과 같이 animals 리스트 내부에서 각각의 요소별로 리스트을 중복으로 만들었다고 할 수 있습니다. 우리가 원하는 것은 리스트가 중복되는 것이 아니라 하나의 스트림에 철자 하나하나가 다 들어가는 것입니다. 이때 사용되는것이 flatMap, 다시 말해 중복된 스트림을 1차원으로 평면화 시키는 메서드입니다.

List<String> results = animals.stream().map(animal -> animal.split(""))
        .flatMap(Arrays::stream)
        .collect(Collectors.toList());

여기서 flatMap 내부의 Arrays::stream은 배열을 스트림으로 변환해주는 메서드 참조 표현입니다. flatMap을 사용하면 각각의 String 리스트를 스트림으로 만드는 것이 아니라, String 리스트를 감싸고 있는 더 큰 스트림의 컨텐츠, 즉 구성 요소로 만들어 줄 수 있습니다.

results = [c, a, t, d, o, g]

이처럼 flatMap을 사용하면 중복 구조로 되어있는 리스트를 하나의 스트림처럼 다룰 수 있습니다.

'JAVA > Java' 카테고리의 다른 글

Stream.reduce()  (1) 2024.07.15
LocalDate (LocalDate to Date, Date to LocalDate)  (0) 2023.08.24
[RegExp] 전화번호, 휴대폰번호, 주민번호 정규식  (0) 2021.09.10

1. Stream.reduce()

Stream.reduce(accumulator) 함수는 Stream의 요소들을 하나의 데이터로 만드는 작업을 수행합니다.

예를 들어, Stream에서 1부터 10까지 숫자가 전달될 때, 이 값을 모두 합하여 55의 결과가 리턴되도록 만들 수 있습니다. 여기서 연산을 수행하는 부분은 accumulator 함수이며, 직접 구현해서 인자로 전달해야 합니다.

아래는 reduce()를 사용하여 Stream에서 전달되는 요소들의 숫자를 모두 합하는 예제입니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

Output:

sum: 55

위의 예제를 설명하면서 reduce()의 동작 방식에 대해서 소개하겠습니다.

reduce()는 인자로 BinaryOperator 객체를 받는데, BinaryOperator는 T 타입의 인자 두개를 받고 T 타입의 객체를 리턴하는 함수형 인터페이스입니다.

BinaryOperator는 (total, n) -> total + n와 같은 형식으로 인자가 전달되는데요. Stream의 1이 전달될 때, total(0) + n(1) = 1와 같이 계산되고, 여기서 리턴되는 1이 다음에 Stream에서 2가 전달될 때 total로 전달됩니다. 따라서, total(1) + n(2) = 3이 됩니다.

다시 정리하면 아래와 같이 연산되면서, 마지막에는 1에서 10까지의 숫자를 더한 55가 리턴됩니다.

  • total(0) + n(1) = 1
  • total(1) + n(2) = 3
  • total(3) + n(3) = 6
  • total(6) + n(4) = 10
  • total(10) + n(5) = 15
  • total(15) + n(6) = 21
  • total(21) + n(7) = 28
  • total(28) + n(8) = 36
  • total(36) + n(9) = 45
  • total(45) + n(10) = 55

1.1 메소드 레퍼런스로 구현

(x, y) -> x + y와 같이 합을 계산하는 함수는, JDK에서 Integer.sum(a, b) 라는 기본 함수를 제공합니다.

여기서 메소드 레퍼런스를 이용하면, Integer::sum 처럼 더 짧은 코드로 동일한 결과를 리턴하는 코드를 구현할 수 있습니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce(Integer::sum);
sum.ifPresent(s -> System.out.println("sum: " + s));

2. 초기값이 있는 Stream.reduce()

위의 예제에서는 total의 초기 값이 0이였습니다.

하지만 Stream.reduce(init, accumulator) 처럼 초기 값을 인자로 전달할 수 있습니다.

위의 예제에서 초기 값이 10으로 설정하면, '10 + 1 + 2 + 3... 10'과 같이 연산을 합니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(10, (total, n) -> total + n);
System.out.println("sum: " + sum);

Output:

sum: 65

3. reduce()의 병렬 처리

Stream.parallel()은 Stream 연산을 병렬 처리로 수행하도록 합니다. 즉, parallel()과 함께 reduce()를 사용하면 순차적으로 연산을 수행하지 않고 여러개의 연산을 동시에 진행하고, 그 작업들을 다시 병합하여 최종적으로 1개의 결과를 생성합니다.

예를 들어, (1 + 2) + (3 + 4) + ... + (9 + 10) 처럼 두개씩 묶어서 먼저 계산하고, 그 결과들을 다시 계산할 수 있습니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.parallel().reduce(0, (total, n) -> total + n);
System.out.println("sum: " + sum);

Output:

sum: 55

하지만 빼기 연산의 경우 병렬처리는 순차적인 처리(병렬이 아닌)와 결과가 다릅니다. 아래 코드를 실행해보면 -55가 아니라 -5가 리턴됩니다. 결과가 다른 이유는 (1 - 2) - (3 - 4) - ... - (9 - 10) 처럼 연산이 수행되면서 순차적으로 연산하는 것과 결과가 달라지기 때문입니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.parallel().reduce(0, (total, n) -> total - n);
System.out.println("sum: " + sum);

Output:

sum: -5

병렬 처리 과정에서 reduce()를 사용할 때는 위와 같은 문제가 없는지 확인을 해야 합니다.

4. 병렬 처리에서 reduce()는 순차적으로 처리

병렬처리에서 연산 순서에 따라 발생하는 문제를 해결하기 위해, 아래 예제와 같이 다른 규칙을 추가할 수 있습니다.

위의 예제와 비교해보면 (total1, total2) -> total1 + total2가 추가되었는데, 병렬로 처리된 결과들의 관계를 나타냅니다. 다시 설명하면, 첫번째 연산과 두번째 연산은 합해야 한다는 규칙을 추가한 것인데요. 이렇게 규칙을 추가하면, 첫번째 연산의 결과가 다음 연산에 영향을 주기 때문에 reduce()는 작업을 나눠서 처리할 수 없게 됩니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = numbers.reduce(0,
        (total, n) -> total - n,
        (total1, total2) -> total1 + total2);
System.out.println("sum: " + sum);

Output:

sum: -55

git gc 명령 실행 시 bad ref, bad object 오류 발생시

1) .git/logs/refs/remotes/origin/해당 브랜치명 ==> 파일을 다른 경로로 이동

2) git gc 명령 실행

하면 해결된다.

+ Recent posts