Java

[Java] Stream

noahkim_ 2023. 10. 22. 16:36

1. Stream

  • 집계연산을 지원하는 일련의 데이터 소스 객체입니다.
  • 데이터를 저장하는 용도가 아닌 계산하는 용도로 사용됩니다.
  • 데이터의 변환과 계산을 위한 중간 연산과 최종 연산을 제공합니다.
    • 컬렉션과 다르게 내부적으로 연산이 수행됩니다.
  • 병렬화하여 다중 코어 아키텍처를 활용하는데 유용합니다.

 

2. 선언형 프로그래밍과 Stream

  • Stream 객체를 사용하여 데이터 처리를 섬세하게 표현할 수 있습니다.
  • 데이터 처리 패턴이 선언적인 연산인 SQL과 유사합니다.
  • 함수형 프로그래밍과 선언형 연산을 지원하여 구현을 감추고 기능을 명시적으로 표현할 수 있습니다.

 

3. 파이프라인 및 연산 체이닝

  • Stream은 더 큰 파이프라인 체이닝을 형성하기 위해 자기 자신을 리턴합니다.
  • 연산의 효율성을 위한 게으른 연산을 수행하며, 최종 연산시에만 결과 데이터가 응답됩니다.
  • 집계연산의 연결은 마치 데이터에 대한 쿼리를 작성하는 것과 같습니다.

 

4. 병렬 처리 및 효율성

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());
  • 특별한 코드를 작성하지 않고 데이터를 병렬로 처리하는 기능을 제공합니다.
  • 특히 대용량 데이터를 효과적으로 처리할 수 있습니다.

 

5. Stream 연산

중간연산

  • 반환형은 Stream이며, 서로 연결하여 파이프라인을 형성하는 것이 목적입니다.
  • 중간 연산은 게으르게 수행됩니다.
    • 스트림 파이프라인에 종단 연산이 호출될 때까지 중간연산은 어떤 처리도 수행되지 않습니다.
    • 이를 통해 단일 순회가 가능하므로 효율성이 크게 향상됩니다.

 

Filtering
  • filter(Predicate), distinct, limit, skip
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> twoEvenSquares = 
    numbers.stream()
           .filter(n -> {
                    System.out.println("filtering " + n); 
                    return n % 2 == 0;
                  })
           .limit(2)
           .collect(toList());

 

 

Mapping
  • map
List<Integer> transactionIds = 
    transactions.stream()
                .map(Transaction::getId)
                .collect(toList());

 

최종연산

  • Stream 파이프라인을 종료하며 결과를 생성합니다.

 

Finding and Matching
boolean expensive =
    transactions.stream()
                .allMatch(t -> t.getValue() > 100);
  • anyMatch, allMatch, noneMatch, findAny, findFirst

 

Reducing
int product = numbers.stream().reduce(1, (a, b) -> a * b);
int product = numbers.stream().reduce(1, Integer::max);
  • reduce 연산은 스트림의 모든 요소를 반복적으로 조합하여 결과를 생성합니다.
  • 초기값과 연산자 두 개의 매개변수를 사용하여 스트림의 모든 요소를 조합합니다.

 

Collect
  • Stream의 요소를 수집하는데 사용됩니다.
  • 다양한 결과 형태로 Stream을 변환할 때 사용됩니다.
  • Collectors 클래스에서 수집 연산을 지원하는 다양한 정적 메소드를 제공합니다.

 

수집하기
stream.collect(Collectors.toList());
stream.collect(Collectors.toSet());
stream.collect(Collectors.toMap(
    Function.identity(),  // 키로 사용될 함수
    String::length        // 값으로 사용될 함수
));

 

연산
stream.collect(Collectors.joining(", "));
stream.collect(Collectors.averagingInt(Integer::intValue));
stream.collect(Collectors.groupingBy(String::length));

 

6. Numeric Streams

  • 박싱 작업의 오버헤드를 줄이기 위해 도입되었습니다.
  • IntStream, DoubleStream, LongStream 등이 있습니다.
  • 기본 데이터 타입에 특화된 연산을 제공하여 성능을 개선합니다. (sum, average, rangeClosed 등)
int statementSum = 
    transactions.stream()
                .mapToInt(Transaction::getValue)
                .sum(); // works!
IntStream oddNumbers = 
    IntStream.rangeClosed(10, 30)
             .filter(n -> n % 2 == 1);

 

 

7. Stream 생성 방법

  • 컬렉션, 배열, 값, 파일, 함수 등 다양한 방법으로 생성할 수 있습니다.
Stream<Integer> numbersFromValues = Stream.of(1, 2, 3, 4);
int[] numbers = {1, 2, 3, 4};
IntStream numbersFromArray = Arrays.stream(numbers);
long numberOfLines = 
    Files.lines(Paths.get(“yourFile.txt”), Charset.defaultCharset())
         .count();

 

유한스트림

  • 명확한 시작과 끝이 있는 스트림을 의미합니다.
  • 대부분의 스트림 소스에서 생성되는 스트림은 유한 스트림입니다.

 

무한스트림

  • 시작은 있지만 끝은 없는 스트림을 의미합니다.
  • Stream.iterate 또는 Stream.generate 메서드를 사용하여 생성됩니다.
    • 크기를 제한하기 위해 limit 메서드를 사용해야 합니다.
Stream<Integer> infiniteStream1 = Stream.iterate(0, n -> n + 1);
infiniteStream1.limit(5).forEach(System.out::println);  // 0, 1, 2, 3, 4를 출력합니다.

 

 

 

 

출처