[Java] Chapter 17. 스트림 요소 처리

SeungWoo Cha·2025년 9월 16일

[Java] 이것이 자바다

목록 보기
16/17

Chapter 17. 스트림 요소 처리

17.1. 스트림이란?

1. 정의

  • 컬렉션 및 배열의 요소를 반복 처리하기 위해 기존에는 for문이나 Iterator를 사용
  • Java 8부터는 스트림(Stream)을 이용해 반복 처리 가능
  • "스트림"은 데이터 요소들이 하나씩 흘러가며 처리된다는 의미

2. 특징

  • 내부 반복자: 처리 속도가 빠르고 병렬 처리에 효율적
  • 람다식 지원: 다양한 요소 처리를 간단히 정의 가능
  • 파이프라인 구조: 중간 처리와 최종 처리를 연결하여 구성 가능

17.2. 내부 반복자

1. 외부 반복자

  • for문, Iterator외부에서 직접 요소를 꺼내와 처리
  • 개발자가 데이터 추출과 처리 코드를 모두 작성해야 함

2. 내부 반복자

  • 스트림은 내부 반복자 방식 사용
  • 데이터 처리 방법(람다식)을 컬렉션 내부로 전달하여 내부적으로 반복 처리
  • 멀티코어 CPU 활용: 요소를 분배해 병렬 처리 가능
  • 따라서 외부 반복자보다 효율적

17.3. 중간 처리와 최종 처리

  • 스트림은 여러 단계로 연결 가능 → 이를 스트림 파이프라인이라 함

  • 중간 처리: 필터링, 매핑, 정렬 등

  • 최종 처리: 반복, 집계(카운트, 합계, 평균 등)

    주의: 최종 처리가 없으면 스트림은 동작하지 않음

  • 메소드 체이닝 패턴을 이용하면 코드가 간결해짐


17.4. 리소스로부터 스트림 얻기

1. 스트림 종류

  • java.util.stream 패키지 제공
  • 부모 인터페이스: BaseStream
  • 주요 자식 인터페이스: Stream, IntStream, LongStream, DoubleStream

2. 생성 방법

(1) 컬렉션에서 얻기

Stream<Product> stream = list.stream();
stream.forEach(p -> System.out.println(p));
  • Collection.stream()
  • Collection.parallelStream()

(2) 배열에서 얻기

  • Arrays.stream(T[])
  • Stream.of(T[])
String[] arr = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);

(3) 숫자 범위에서 얻기

  • IntStream.range(start, end) → end 불포함
  • IntStream.rangeClosed(start, end) → end 포함
IntStream.range(1, 5).forEach(System.out::print);   // 1234
IntStream.rangeClosed(1, 5).forEach(System.out::print); // 12345

(4) 파일에서 얻기

Path path = Paths.get("data.txt");
Stream<String> stream = Files.lines(path, Charset.defaultCharset());
stream.forEach(System.out::println);
stream.close();

(5) 기타

  • Files.list(Path) → 디렉토리 내 경로 스트림
  • Random.ints(), Random.longs(), Random.doubles() → 랜덤 수 스트림

17.5. 요소 걸러내기 (Filtering)

주요 메소드

  1. distinct(): 중복 제거

  2. filter(): 조건에 맞는 요소만 걸러냄

    • Stream.filter(Predicate<T>)
    • IntStream.filter(IntPredicate)
    • LongStream.filter(LongPredicate)
    • DoubleStream.filter(DoublePredicate)

함수형 인터페이스 (람다식 사용 가능)

  • Predicate<T>: boolean test(T t)
  • IntPredicate: boolean test(int value)
  • LongPredicate: boolean test(long value)
  • DoublePredicate: boolean test(double value)

17.6. 요소 변환 (Mapping)

  • 스트림 요소를 다른 형태로 변환하는 중간 처리 기능

1. 요소 → 다른 요소

  • map(Function<T,R>)
  • mapToInt(ToIntFunction<T>)
  • mapToLong(ToLongFunction<T>)
  • mapToDouble(ToDoubleFunction<T>)

2. 기본 타입 변환

  • asLongStream() → int → long
  • asDoubleStream() → int/long → double
  • boxed() → 기본 타입 → Wrapper 클래스

3. 다중 요소 변환 (flatMap)

  • 하나의 요소를 여러 개의 요소로 변환
List<String> list = Arrays.asList("a b c", "d e f");
list.stream()
    .flatMap(str -> Arrays.stream(str.split(" ")))
    .forEach(System.out::println);

17.7. 요소 정렬 (Sorting)

1. 주요 메소드

  • Stream<T> sorted() → 기본 Comparable 기준 정렬
  • Stream<T> sorted(Comparator<T>) → 지정된 Comparator 기준 정렬
  • IntStream.sorted(), LongStream.sorted(), DoubleStream.sorted() → 오름차순 정렬

2. 객체 정렬

  • 객체가 Comparable을 구현해야 sorted() 사용 가능
  • 내림차순 정렬: Comparator.reverseOrder()

3. Comparator 이용

studentList.stream()
    .sorted((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()))
    .forEach(System.out::println);

17.8. 요소를 하나씩 처리 (루핑)

  • 최종 처리 기능 중 하나는 루핑(looping)
  • 단순히 요소를 반복해서 처리할 때 사용

메소드

  • forEach(Consumer<T> action)
    → 스트림의 모든 요소를 순차적으로 처리
  • forEachOrdered(Consumer<T> action)
    → 병렬 스트림 사용 시에도 순서를 보장
List<String> list = Arrays.asList("a", "b", "c");
list.stream().forEach(System.out::println);

17.9. 요소 조건 만족 여부 (매칭)

  • 스트림의 요소들이 특정 조건을 만족하는지 여부를 검사

메소드

  • boolean allMatch(Predicate<T>)
    → 모든 요소가 조건을 만족하면 true
  • boolean anyMatch(Predicate<T>)
    → 하나라도 조건을 만족하면 true
  • boolean noneMatch(Predicate<T>)
    → 모든 요소가 조건을 만족하지 않으면 true
List<Integer> list = Arrays.asList(2, 4, 6);
boolean result = list.stream().allMatch(n -> n % 2 == 0);
System.out.println(result); // true

17.10. 요소 기본 집계

  • 집계(Aggregate): 스트림의 최종 처리 기능 중 하나
  • 카운팅, 합계, 평균, 최대값, 최소값 등을 구함

메소드

  • long count()
  • Optional<T> max(Comparator<T>)
  • Optional<T> min(Comparator<T>)
  • OptionalDouble average()
  • sum() (IntStream, LongStream, DoubleStream 전용)
IntStream stream = IntStream.of(1, 2, 3, 4, 5);
int sum = stream.sum();
System.out.println(sum); // 15

17.11. 요소 커스텀 집계 (reduce)

  • reduce() 메소드를 이용해 직접 정의한 방법으로 집계 가능

메소드 형태

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • T reduce(T identity, BinaryOperator<T> accumulator)

예제

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

// 합계 구하기 (초기값 0)
int sum = list.stream()
              .reduce(0, (a, b) -> a + b);
System.out.println(sum); // 15

17.12. 요소 수집 (collect)

  • 스트림 요소들을 원하는 자료구조로 수집할 때 사용

주요 메소드

  • Collectors.toList()
  • Collectors.toSet()
  • Collectors.toMap(keyMapper, valueMapper)
  • Collectors.joining(delimiter)
  • Collectors.groupingBy(Function<T,K>) → 그룹핑
  • Collectors.partitioningBy(Predicate<T>) → 조건에 따라 분할

예제

List<String> names = Arrays.asList("Kim", "Lee", "Park");

// 리스트로 수집
List<String> result = names.stream()
                           .filter(n -> n.length() >= 3)
                           .collect(Collectors.toList());

System.out.println(result); // [Kim, Lee, Park]

17.13. 요소 병렬 처리

  • 스트림은 내부적으로 ForkJoinPool을 사용하여 병렬 처리 지원
  • 데이터가 많고 CPU 코어가 여러 개일 경우 성능 향상 가능

병렬 처리 방법

  • parallelStream() → 컬렉션에서 병렬 스트림 생성
  • stream().parallel() → 기존 스트림을 병렬 스트림으로 변환
List<String> list = Arrays.asList("a", "b", "c", "d");

// 병렬 처리 (순서 보장 안 됨)
list.parallelStream().forEach(System.out::println);

// 순서 보장 병렬 처리
list.parallelStream().forEachOrdered(System.out::println);

  • 루핑: forEach, forEachOrdered
  • 매칭: allMatch, anyMatch, noneMatch
  • 기본 집계: count, sum, average, max, min
  • 커스텀 집계: reduce()
  • 수집: collect(), Collectors API 활용
  • 병렬 처리: parallelStream(), parallel()

profile
한 발자국씩

0개의 댓글