[Java] 스트림과 병렬 처리 ④

kiteB·2022년 3월 4일
0

Java2

목록 보기
19/36
post-thumbnail

[ 매핑 (flatMapXXX(), mapXXX(), asXXXStream(), boxed()) ]

매핑(mapping)은 중간 처리 기능으로 스트림의 요소를 다른 요소로 대체하는 작업을 말한다.

스트림에서 제공하는 매핑 메소드는 flatXXX(), mapXXX(), asDoubleStream(), asLongStream(), boxed()가 있다.


1. flatMapXXX() 메소드

flatMapXXX() 메소드는 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다.

스트림에서 A라는 요소는 A1, A2 요소로 대체되고, B라는 요소는 B1, B2로 대체된다고 가정했을 경우,
A1, A2, B1, B2 요소를 가지는 새로운 스트림이 생성된다.

✅ flatMapXXX() 메소드

✅ 예제

입력된 데이터(요소)들이 List<String>에 저장되어 있다고 가정하고, 요소별로 단어를 뽑아 단어 스트림으로 재생성한다. 만약 입력된 데이터들이 숫자라면 숫자를 뽑아 숫자 스트림으로 재생성한다.

import java.util.Arrays;
import java.util.List;

public class FlatMapExample {
    public static void main(String[] args) {
        List<String> inputList1 = Arrays.asList("java8 lambda", "stream mapping");
        inputList1.stream()
                .flatMap(data -> Arrays.stream(data.split(" ")))
                .forEach(word -> System.out.println(word));

        System.out.println();

        List<String> inputList2 = Arrays.asList("10, 20, 30", "40, 50, 60");
        inputList2.stream()
                .flatMapToInt(data -> {
                    String[] strArr = data.split(",");
                    int[] intArr = new int[strArr.length];
                    for (int i = 0; i < strArr.length; i++) {
                        intArr[i] = Integer.parseInt(strArr[i].trim());
                    }
                    return Arrays.stream(intArr);
                })
                .forEach(number -> System.out.println(number));
    }
}
  • 실행 결과
java8
lambda
stream
mapping

10
20
30
40
50
60

2. mapXXX() 메소드

mapXXX() 메소드는 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다.

A 요소는 C 요소로 대체되고, B 요소는 D 요소로 대체된다고 했을 경우,
C, D 요소를 가지는 새로운 스트림이 생성된다.

✅ mapXXX() 메소드 종류

✅ 예제

학생 List에서 학생의 점수를 요소로 하는 새로운 스트림을 생성하고, 점수를 순차적으로 콘솔에 출력한다.

import java.util.Arrays;
import java.util.List;

public class MapExample {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("김", 10),
                new Student("이", 20),
                new Student("박", 30)
        );

        studentList.stream()
                .mapToInt(Student :: getScore)
                .forEach(score -> System.out.println(score));

    }
}
  • 실행 결과
10
20
30

3. asDoubleStream(), asLongStream(), boxed() 메소드

  • asDoubleStream() 메소드는 IntStream의 int 요소 또는 LongStream의 long 요소를 double 요소로 타입 변환해서 DoubleStream을 생성한다.
  • asLongStream() 메소드는 IntStream의 int 요소를 long 요소로 타입 변환해서 LongStream을 생성한다.
  • boxed() 메소드는 int, long, double 요소를 Integer, Long, Double 요소로 박싱해서 Stream을 생성한다.

✅ 예제 | 다른 요소로 대체

int[] 배열로부터 IntStream을 얻고 난 다음 int 요소를 double 요소로 타입 변환해서 DoubleStream을 생성한다.
또한 int 요소를 Integer 객체로 박싱해서 Stream<Integer>를 생성한다.

import java.util.Arrays;
import java.util.stream.IntStream;

public class AsDoubleStreamAndBoxedExample {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3, 4, 5};

        IntStream intStream = Arrays.stream(intArray);
        intStream
                .asDoubleStream()
                .forEach(d -> System.out.println(d));

        System.out.println();

        intStream = Arrays.stream(intArray);
        intStream
                .boxed()
                .forEach(obj -> System.out.println(obj.intValue()));
    }
}
  • 실행 결과
1.0
2.0
3.0
4.0
5.0

1
2
3
4
5

[ 정렬 (sorted()) ]

스트림은 요소가 최종 처리되기 전에 중간 단계에서 요소를 정렬해서 최종 처리 순서를 변경할 수 있다.

✅ 요소 정렬 메소드

객체 요소일 경우에는 클래스가 Comparable을 구현하지 않으면 sorted() 메소드를 호출했을 때 ClassCastException이 발생하기 때문에 Comparable을 구현한 요소에서만 sorted() 메소드를 호출해야 한다.


✅ 예제 | 정렬 가능한 클래스

점수를 기준으로 Student 요소를 오름차순으로 정렬하기 위해 Comparable을 구현했다.

  • Student
public class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }
    
    @Override
    public int compareTo(Student o) {
        return Integer.compare(score, o.score);
    }
}

✅ 다양한 정렬 방법

  • 객체 요소가 Comparable을 구현한 상태에서 기본 비교(Comparable) 방법으로 정렬하고 싶다면 다음 세 가지 방법 중 하나를 선택해서 sorted()를 호출하면 된다.
sorted();
sorted( (a, b) -> a.compareTo(b) );
sorted( Comparator.naturalOrder() );
  • 만약 객체 요소가 Comparable을 구현하고 있지만, 기본 비교 방법과 정반대 방법으로 정렬하고 싶다면 다음과 같이 sorted()를 호출하면 된다.
sorted( (a, b) -> a.compareTo(b) );
sorted( Comparator.reverseOrder() );
  • 객체 요소가 Comparable을 구현하고 있지 않았다면 Comparator를 매개값으로 갖는 sorted() 메소드를 사용하면 된다. Comparator는 함수적 인터페이스이므로 다음과 같이 람다식으로 매개값을 작성할 수 있다.
sorted( (a, b) -> { ... } )

중괄호 {} 안에는 a와 b를 비교해서

  • a가 작으면 음수
  • 같으면 0
  • a가 크면 양수
    를 리턴하는 코드를 작성하면 된다.

✅ 예제 | 정렬

  • 숫자 요소일 경우에는 오름차순으로 정렬한 후 출력한다.
  • Student 요소일 경우에는 Student의 기본 비교 (Comparable) 방법을 이용해서 점수를 기준으로 오름차순으로 정렬한 후 출력한다.
  • 마지막으로 Comparator를 제공해서 내림차순으로 정렬한 후 출력한다.
  • SortingExample
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;

public class SortingExample {
    public static void main(String[] args) {
        //숫자 요소일 경우
        IntStream intStream = Arrays.stream(new int[] {5, 3, 2, 1, 4});
        intStream
                .sorted()
                .forEach(n -> System.out.print(n + ", "));
        System.out.println();

        //객체 요소일 경우
        List<Student> studentList = Arrays.asList(
                new Student("김", 30),
                new Student("이", 10),
                new Student("박", 20)
        );

        studentList.stream()
                .sorted()
                .forEach(s -> System.out.print(s.getScore() + ", "));
        System.out.println();

        studentList.stream()
                .sorted(Comparator.reverseOrder())
                .forEach(s -> System.out.print(s.getScore() + ", "));
    }
}
  • 실행 결과
1, 2, 3, 4, 5,
10, 20, 30,
30, 20, 10,

[ 루핑 (peek(), forEach()) ]

루핑(looping)은 요소 전체를 반복하는 것을 말한다.

루핑하는 메소드에는 peek(), forEach()가 있다. 이 두 메소드는 루핑한다는 기능에서는 동일하지만, 동작 방식은 다르다. peek()은 중간 처리 메소드이고, forEach()는 최종 처리 메소드이다.


✅ peek()

peek()은 중간 처리 단계에서 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용된다.

최종 처리 메소드가 실행되지 않으면 지연되기 때문에 반드시 최종 처리 메소드가 호출되어야 동작한다.

예를 들어 필터링 후 어떤 요소만 남았는지 확인하기 위해 다음과 같이 peek()를 마지막에서 호출할 경우, 스트림은 전혀 동작하지 않는다.

intStream
        .filter(a -> a % 2 == 0)
        .peek(a -> System.out.print(a))

요소 처리의 최종 단계가 합을 구하는 것이라면, peek() 메소드 호출 후 sum()을 호출해야만 peek()가 정상적으로 동작한다.

intStream
        .filter(a -> a % 2 == 0)
        .peek(a -> System.out.print(a))
        .sum()

✅ forEach()

forEach()는 최종 처리 메소드이기 때문에 파이프라인 마지막에 루핑하면서 요소를 하나씩 처리한다.

forEach()는 요소를 소비하는 최종 처리 메소드이므로 이후에 sum()과 같은 다른 최종 메소드를 호출하면 안된다.

✅ 예제 | 루핑

  • LoopingExample
import java.util.Arrays;

public class LoopingExample {
    public static void main(String[] args) {
        int[] intArr = {1, 2, 3, 4, 5};

        System.out.println("[peek()를 마지막에 호출한 경우]");
        Arrays.stream(intArr)
                .filter(a -> a % 2 == 0)
                .peek(n -> System.out.println(n));  //동작하지 않는다.

        System.out.println("[최종 처리 메소드를 마지막에 호출한 경우]");
        int total = Arrays.stream(intArr)
                .filter(a -> a % 2 == 0)
                .peek(n -> System.out.println(n))   //동작한다.
                .sum();                             //최종 메소드
        System.out.println("총합: " + total);

        System.out.println("[forEach()를 마지막에 호출한 경우]");
        Arrays.stream(intArr)
                .filter(a -> a % 2 == 0)
                .forEach(n -> System.out.println(n));   //최종 메소드로 동작한다.

    }
}
  • 실행 결과
[peek()를 마지막에 호출한 경우]
[최종 처리 메소드를 마지막에 호출한 경우]
2
4
총합: 6
[forEach()를 마지막에 호출한 경우]
2
4

[ 참고자료 ]

이것이 자바다 책

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글