Java

자바 Stream API: 데이터 처리를 더 효율적으로

본명은이점례 2023. 10. 6. 21:54
728x90

자바 8에서 소개된 Stream API는 다양한 데이터 소스(컬렉션, 배열 등)를 표준화된 방법으로 다루는 강력한 도구입니다. 이 블로그 글에서는 자바 Stream API의 주요 특징과 사용법을 자세히 알아보겠습니다.

 

스트림(Stream)이란 무엇인가요?

스트림은 자바 8에서 도입된 데이터 처리 방법 중 하나로, 다양한 데이터 소스를 표준화된 방법으로 다룰 수 있게 해줍니다. 이전에는 List, Set, Map 등의 사용 방법이 각각 다르기 때문에 데이터 처리 코드가 복잡해졌습니다. 스트림은 이런 불편함을 해소하고 데이터 소스를 통일된 방식으로 다룰 수 있도록 합니다.

 

스트림은 데이터 소스를 스트림으로 변환한 후 여러 번의 중간 연산과 최종 연산을 통해 데이터를 처리합니다. 이를 통해 데이터 처리를 효율적으로 수행할 수 있습니다.

 

스트림의 처리 순서는 다음과 같습니다:

  1. 스트림 만들기
  2. 중간 연산(반복 적용 가능하며, 연산 결과가 스트림)
  3. 최종 연산 (스트림의 요소를 소모하고 결과 반환)

예를 들어, 다음 코드는 리스트에서 스트림을 생성하고 중간 연산과 최종 연산을 수행하는 예제입니다.

list.stream()        // 스트림 만들기
    .distinct()      // 중간 연산
    .limit(5)        // 중간 연산
    .sorted()        // 중간 연산
    .forEach(System.out::println); // 최종 연산

 

스트림의 특징

 

스트림의 특징을 알아보겠습니다.

  • 스트림은 데이터를 담고 있는 저장소(컬렉션)가 아닙니다. 따라서 스트림은 원본 데이터 소스를 변경하지 않고 읽기 전용(read-only)입니다.
  • 스트림은 일회용입니다. 한 번 사용하면 재사용할 수 없으며, 필요한 경우 다시 스트림을 생성해야 합니다.
  • 최종 연산을 호출하기 전까지 중간 연산은 실제로 수행되지 않습니다. 이를 "게으른(lazy)" 연산이라고 합니다.
  • 스트림은 무제한할 수도 있으며, Short Circuit 메소드를 사용하여 제한할 수 있습니다.
  • 병렬 처리가 쉽게 가능하며 멀티 스레드를 활용할 수 있습니다. (.parallel 메소드를 사용하여 병렬 스트림으로 변환)
  • 기본형 스트림(IntStream, LongStream, DoubleStream)이 제공되며, 오토박싱과 언박싱 과정을 줄여 효율적으로 처리할 수 있습니다.

 

스트림 사용하기

 

1. 스트림 생성

스트림을 생성하는 방법은 다양합니다.

  • 컬렉션을 스트림으로 변환: collection.stream()
  • 객체 배열로부터 스트림 생성: Stream.of(elements)
  • 배열에서 스트림 생성: Arrays.stream(array)
  • 람다식 iterate() 또는 generate()

예를 들어, 다음은 리스트를 스트림으로 변환하는 방법입니다.

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

 

2. 스트림의 중간 연산

중간 연산은 스트림의 요소를 변환하거나 필터링하는 등의 작업을 수행합니다. 몇 가지 중요한 중간 연산은 다음과 같습니다:

  • distinct(): 중복 요소 제거
  • filter(Predicate<T> predicate): 조건에 맞지 않는 요소 필터링
  • limit(long maxSize): 최대 요소 개수 제한
  • skip(long n): 앞에서부터 n개 요소 건너뛰기
  • sorted(): 요소를 기본 정렬로 정렬
  • sorted(Comparator<T> comparator): 요소를 지정한 조건에 따라 정렬
  • map(Function<T, R> mapper): 요소를 다른 형태로 변환
  • peek(Consumer<T> action): 요소에 작업 수행하며 중간 결과 확인
  • flatMap(Function<T, Stream<R>> mapper): 스트림의 스트림을 펼쳐서 하나의 스트림으로 변환

 

3. 스트림의 최종 연산

최종 연산은 스트림의 요소를 소모하고 결과를 반환하는 작업을 수행합니다. 몇 가지 중요한 최종 연산은 다음과 같습니다:

  • forEach(Consumer<? super T> action): 각 요소에 작업 수행
  • forEachOrdered(Consumer<? super T> action): 병렬 스트림에서 순서 유지하며 작업 수행
  • count(): 스트림의 요소 개수 반환
  • max(Comparator<? super T> comparator): 스트림의 최대값 반환
  • min(Comparator<? super T> comparator): 스트림의 최소값 반환
  • findAny(): 아무 요소나 반환

(병렬 스트림에 유용)

  • findFirst(): 첫 번째 요소 반환 (순차 스트림에 유용)
  • allMatch(Predicate<T> p): 모든 요소가 조건을 만족하는지 확인
  • anyMatch(Predicate<T> p): 하나 이상의 요소가 조건을 만족하는지 확인
  • noneMatch(Predicate<T> p): 모든 요소가 조건을 만족하지 않는지 확인
  • toArray(): 모든 요소를 배열로 반환
  • toArray(IntFunction<A[]> generator): 특정 타입의 배열로 반환
  • reduce(BinaryOperator<T> accumulator): 스트림의 요소를 하나씩 줄여가며 계산
  • collect(Collector<? super T, A, R> collector): 스트림의 요소를 원하는 자료형으로 변환

 

스트림 API 사용 예시

 

데이터 필터링 (Filter)

스트림을 사용하면 데이터 필터링이 간편해집니다. 예를 들어, 이름이 3글자 이상인 데이터만 새로운 스트림으로 변경하는 코드는 다음과 같습니다.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
Stream<String> filteredNames = names.stream()
    .filter(name -> name.length() >= 3);

데이터 변환 (Map)

스트림의 map 연산을 사용하여 데이터를 원하는 형태로 변환할 수 있습니다. 예를 들어, 파일 목록에서 파일 이름만 추출하는 코드는 다음과 같습니다.

List<File> files = Arrays.asList(new File("file1.txt"), new File("file2.txt"), new File("file3.txt"));
Stream<String> fileNames = files.stream()
    .map(File::getName);

요소 제한 (Limit)과 건너뛰기 (Skip)

스트림에서 일부 요소만 선택하려면 limit과 skip 연산을 사용할 수 있습니다. 아래 예제는 스트림에서 처음 3개 요소를 선택하고, 다음 2개 요소는 건너뛰는 예제입니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> limitedNumbers = numbers
    .limit(3)  // 처음 3개 요소 선택
    .skip(2);  // 처음 2개 요소 건너뛰기

조건 검사 (anyMatch, allMatch, noneMatch)

스트림의 요소들 중에서 특정 조건을 만족하는지 확인할 때 anyMatch, allMatch, noneMatch 연산을 사용할 수 있습니다. 아래 예제는 스트림에 "Java"라는 단어가 하나라도 포함되어 있는지 확인합니다.

List<String> topics = Arrays.asList("Java Programming", "Python Basics", "Machine Learning");
boolean containsJava = topics.stream()
    .anyMatch(topic -> topic.contains("Java"));

reduce와 collect 연산

reduce 연산은 스트림의 요소를 하나씩 줄여가며 누적 연산을 수행합니다. 아래 예제는 스트림의 모든 요소를 더하는 방법을 보여줍니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.reduce((total, number) -> total + number);

collect 연산은 스트림의 요소를 원하는 자료형으로 변환하는데 사용됩니다. 예를 들어, 스트림의 문자열 요소를 쉼표로 구분된 하나의 문자열로 변환하는 코드는 다음과 같습니다.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String joinedNames = names.stream()
    .collect(Collectors.joining(", "));

 

 

Stream API는 자바 8부터 도입된 강력한 데이터 처리 도구로, 코드를 간결하고 효율적으로 만들어줍니다. 데이터를 필터링하고 변환하는 등 다양한 작업을 수행할 수 있으며, 병렬 처리 기능을 활용하여 성능을 향상시킬 수 있습니다. Stream API를 마스터하면 자바 애플리케이션의 코드 품질과 유지 보수성을 향상시킬 수 있을 것입니다.

 

728x90