반응형

Java Stream

 

Java Stream 특징

- Java 8부터 도입된 기능으로, 컬렉션, 배열, 파일 등의 데이터 요소를 처리하는 연산을 지원하는 함수형 Stream 입니다.

- 데이터 소스로부터 연속된 데이터 흐름을 제공하여 데이터를 처리합니다.

- 데이터를 효율적으로 처리할 수 있으며, 병렬 처리를 포함한 다양한 연산을 적용할 수 있습니다. 

- 데이터를 필터링, 매핑, 정렬, 그룹핑 등 다양한 작업을 수행할 수 있으며, 함수형 프로그래밍 스타일을 지원하여 코드를 간결하고 가독성 있게 작성할 수 있습니다.

- 중간 연산과 최종 연산으로 구성된 파이프라인을 만들 수 있습니다. 중간 연산은 다른 Stream 을 반환하며, 최종 연산은 최종 결과를 반환합니다.

 

Java Stream 처리 단계

1. 데이터 소스 생성

 - 컬렉션, 배열, 파일 등의 데이터 소스로부터 Stream 을 생성합니다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();

 

2. 중간 연산

 - 필터링, 매핑, 정렬 등의 작업을 수행할 수 있습니다.

stream = stream.filter(n -> n % 2 == 0) // 짝수만 필터링
               .map(n -> n * 2)         // 각 요소를 2배로 매핑
               .sorted();               // 정렬

 

3. 최종 연산

 - 리스트, 배열, 요소 개수 등 다양한 결과를 얻을 수 있습니다.

// 요소 개수 세기(count)
long count = numbers.stream()
                   .count();
System.out.println(count);  // 출력: 5

// 요소들의 합 구하기(sum)
int sum = numbers.stream()
                .mapToInt(Integer::intValue)
                .sum();
System.out.println(sum);  // 출력: 15


// 최대값 찾기(max)
Optional<Integer> max = numbers.stream()
                               .max(Comparator.naturalOrder());
System.out.println(max.orElse(0));  // 출력: 5


// 요소들의 평균 구하기(average)
OptionalDouble average = numbers.stream()
                               .mapToInt(Integer::intValue)
                               .average();
System.out.println(average.orElse(0));  // 출력: 3.0


// 요소들을 리스트로 변환(collect)
List<Integer> doubledNumbers = numbers.stream()
                                      .map(n -> n * 2)
                                      .collect(Collectors.toList());
System.out.println(doubledNumbers);  // 출력: [2, 4, 6, 8, 10]

※ 이외에도 최소값 찾기(min), 문자열로 연결하기(join), 그룹화하기(groupingBy) 등 다양한 최종 연산이 있습니다.

 

Java Stream VS Parallel Stream

구분 Stream Parallel Stream
데이터 처리 방식 순차적으로 연산을 수행 병렬로 연산을 수행
처리 속도 단일 CPU 코어에서 동작하며, 작업을 직렬로 처리.  멀티코어 시스템에서 데이터 처리 속도를 높일 수 있음.
작업을 분할하고 스레드 간의 동기화 오버헤드가 발생할 수 있으므로, 작업량이 많거나 데이터가 충분히 큰 경우에 이점.
스레드 안전성 - 멀티 스레드에서 작업을 수행하므로 스레드 안전성에 주의.
thread-safe 한 자료구조를 사용하거나 동기화 메커니즘을 적절하게 사용
순서 보장 연산 순서가 보장되며, 요소 순서 유지 병렬로 처리하기 때문에 요소의 순서가 보장되지 않을 수 있음. 
사용처 데이터의 순서가 중요하거나 상호작용이 필요한 경우 유용 멀티코어 CPU 시스템에서 병렬 처리에 특히 유용하며, 대량의 데이터를 동시에 처리해야 할 때 성능 향상을 제공

 

소스로 비교

public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // stream
    int sumSequential = Arrays.stream(numbers).sum();
    System.out.println("Sum (Sequential): " + sumSequential);

    // parallel stream
    int sumParallel = Arrays.stream(numbers)
        .parallel()
        .sum();
    System.out.println("Sum (Parallel): " + sumParallel);
}
    
// 결과 출력
Sum (Sequential): 55
Sum (Parallel): 55

stream : Arrays.stream(numbers)를 호출하여 스트림을 생성하고, sum() 메서드를 호출하여 모든 요소의 합을 계산

parallel stream : Arrays.stream(numbers)를 호출하여 스트림을 생성한 후 .parallel() 메서드를 호출하여 병렬 스트림으로 전환, 그리고 나서 sum() 메서드를 호출하여 병렬로 모든 요소의 합을 계산

※  합계를 구하는 간단한 작업. 작은 데이터셋이므로 parallel stream 을 사용하여도 순차 stream 과 동일한 결과.

 

parallel stream 잘못된 사용 예제

public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 잘못된 사용 예제
    int sum = Arrays.stream(numbers)
        .parallel()
        .map(n -> {
            if (n == 5) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return n;
        })
        .sum();
        
    System.out.println("Sum: " + sum);
}

// 결과 출력
Sum: 50

 - 배열 numbers를 데이터 소스로 사용하여 parallel stream을 이용하여 요소들을 처리하고 합계(sum)를 계산

- parallel stream 내부의 map 연산에서 특정 조건(n == 5)에 따라 5인 경우 1초의 지연을 발생

- parallel stream은 요소를 병렬로 처리하기 때문에 map 연산의 각 요소는 병렬로 실행

- 조건 n == 5를 만족하는 요소가 존재할 경우 1초의 지연이 발생하고, 이로 인해 전체 연산에 대한 지연이 발생

 

parallel stream 처리 순서 확인

-public static void main(String[] args) {
    // 데이터 소스
    int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    Arrays.stream(numbers)
        .parallel()
        .map(n -> {
            System.out.println("Map: " + n + " - " + Thread.currentThread().getName());
            return n;
        })
        .forEach(n -> System.out.println("ForEach: " + n + " - " + Thread.currentThread().getName()));
-}

// 결과 출력
Map: 1 - main
Map: 2 - ForkJoinPool.commonPool-worker-1
Map: 5 - ForkJoinPool.commonPool-worker-2
Map: 4 - ForkJoinPool.commonPool-worker-3
Map: 3 - ForkJoinPool.commonPool-worker-4
Map: 6 - main
ForEach: 6 - main
ForEach: 1 - main
ForEach: 2 - ForkJoinPool.commonPool-worker-1
ForEach: 3 - ForkJoinPool.commonPool-worker-4
ForEach: 4 - ForkJoinPool.commonPool-worker-3
ForEach: 5 - ForkJoinPool.commonPool-worker-2

- 배열 numbers를 데이터 소스로 사용하여 parallel stream을 생성하고, map 연산과 forEach 연산을 순차적으로 수행.

- map 연산은 각 요소를 변환하고, forEach 연산은 각 요소를 출력.

- Thread.currentThread().getName() : 현재 실행 중인 스레드의 이름 출력.

- map 연산은 여러 스레드에서 동시에 실행되므로 요소의 처리 순서가 보장되지 않음. 

- forEach 연산은 마지막에 순차적으로 수행되며, 최종 결과인 출력도 순차적으로 출력

※ 중간 연산과 최종 연산의 특성에 따라 다를 수 있으므로, 순서에 의존하는 작업을 수행하는 경우에는 주의가 필요

 

Java 8 에 추가된 기능인 Stream 에 대해서 간략하게 알아보았습니다. 더불어 parallel stream 과의 비교도 확인하였습니다.

반응형

+ Recent posts