Published on
👁️

Java Stream API (미완)

Authors
  • avatar
    Name
    River
    Twitter
51Java 스트림(Stream)이란 무엇이고 어떻게 생성하나요?쉬움
Java 스트림은 배열이나 컬렉션 같은 데이터 묶음을 쉽고 깔끔하게 처리하기 위한 기능입니다. 기존 for문 방식과 달리 '무엇을' 할지만 선언적으로 명시하여 코드가 간결해지며, 원본 데이터를 변경하지 않는 불변성과 지연 평가 특성을 가집니다. 또 스트림은 기본적으로 객체를 다루기 때문에 박싱, 언박싱 과정에서 오버헤드가 발생할 수 있어 원시타입에 대한 특화 스트림을 제공하고 있습니다.
상세 설명

스트림(Stream)이란?

  • 데이터를 처리하는 컨베이어 벨트
  • 데이터 소스를 추상화하여, 데이터를 다루는 방법에 통일성을 제공하는 API
    • 리스트든, 배열이든, 파일이든 모두 .stream()으로 동일하게 처리 가능
  • "어떻게"가 아닌 "무엇을" 처리할지 명시하는 선언형 프로그래밍 지원
    • 전통적인 방식

      List<String> names = new ArrayList<>();
      for (Employee emp : employees) {
          if (emp.getSalary() > 5000) {
              names.add(emp.getName().toUpperCase());
          }
      }
      
    • 스트림

      List<String> names = employees.stream()
          .filter(emp -> emp.getSalary() > 5000)
          .map(emp -> emp.getName().toUpperCase())
          .collect(Collectors.toList());  
      

Java 스트림은 함수형 프로그래밍 패러다임을 Java에 도입한 강력한 도구입니다. 불변성, 지연 평가, 병렬 처리 등의 특성을 활용하여 효율적이고 안전한 데이터 처리가 가능합니다.

스트림의 주요 특징

  1. 불변성 (Immutability)

    • 스트림 연산은 원본 데이터가 아닌 별도의 스트림을 통해 결과를 생성

    • 여러 스레드에서 동시에 사용해도 안전하다.

    • 이러한 특성 덕분에 병렬 처리가 쉽다

      Optional<String> result = words.parallelStream()  // 이것만 바꾸면 된다.
          .filter(word -> expensiveOperation(word))
          .findFirst();
      


  2. 지연 평가 (Lazy Evaluation)

    List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
    
    Stream<String> stream = words.stream()
        .filter(word -> {
            System.out.println("Filtering: " + word); // 이 시점에선 실행 안됨!
            return word.length() > 4;
        })
        .map(word -> {
            System.out.println("Mapping: " + word);   // 이것도 실행 안됨!
            return word.toUpperCase();
        });
    
    System.out.println("스트림 생성 완료!"); // 여기까지는 데이터가 흐르지 않는다.
    
    // 최종 연산을 호출  =>  데이터가 흐른다.
    List<String> result = stream.collect(Collectors.toList());
    
    스트림 생성 완료!
    Filtering: apple
    Mapping: apple
    Filtering: banana
    Mapping: banana
    Filtering: cherry
    Mapping: cherry
    Filtering: date
    
    • 위의 예시처럼 최종 연산이 호출되기 전까지 중간 연산은 실행되지 않는다.

      즉, 스트림 생성은 스트림 설계도를 완성하는 것이지, 데이터를 흘려보내지 않는 상태이다.

    • limit(3)이 있으면 3개만 처리하고 나머지는 아예 건드리지 않는다.

      • limit(), findFirst(), findAny(), anyMatch(), allMatch()
    • 이를 통해 불필요한 연산을 최소화 가능

      • 사실 for문과 break 등의 조합으로도 성능은 거의 비슷하다.


  3. 일회성 (Single Use)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = numbers.stream();
    
    // 첫 번째 사용  =>   5
    long count = stream.count();
    
    // 두 번째 사용  =>  에러 발생!
    int sum = stream.mapToInt(n -> n).sum(); 
    
    • 한 번 정의한 스트림은 1번만 사용할 수 있다

      • 재사용 시 에러 발생 (IllegalStateException)
    • 스트림은 한 번 사용(최종 연산 실행)하면 닫혀서 재사용 불가

    • 필요 시 스트림을 다시 생성해야 한다.

      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
      
      // 매번 새로운 스트림 생성
      long count = numbers.stream().count();
      int sum = numbers.stream().mapToInt(n -> n).sum();
      
      // 또는 Supplier 패턴 사용
      Supplier<Stream<Integer>> streamSupplier = () -> numbers.stream();
      long count2 = streamSupplier.get().count();
      int sum2 = streamSupplier.get().mapToInt(n -> n).sum();
      

스트림 생성 방법

  1. 컬렉션으로부터 생성

    List<String> list = Arrays.asList("a", "b", "c");
    Stream<String> stream = list.stream();
    
    • 가장 일반적인 방법

    • Collection 인터페이스

      Collection (최상위 인터페이스)
      ├── List
      ├── Set  
      └── Queue
          └── Deque (Queue를 상속)
      


  2. Map 객체으로부터 생성

    Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3);
    
    Stream<String> keyStream = map.keySet().stream();      // 키만
    Stream<Integer> valueStream = map.values().stream();   // 값만
    Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); // 키-값 쌍
    
    • Map은 Collection을 상속받지 않는다.
    • Map 자체는 stream() 메서드가 존재하지 않는다.
      • 다른 구조로 변형 후 사용한다.


  3. 배열으로부터 생성

    String[] arr = new String[]{"a", "b", "c"};
    Stream<String> stream = Arrays.stream(arr);
    
    int[] intArr = {1, 2, 3, 4, 5};
    IntStream intStream = Arrays.stream(intArr);
    
    • Arrays.stream() 메서드 사용
    • 기본 타입 배열은 특화된 스트림 생성 가능
      • IntStream, LongStream, DoubleStream 등


  4. 직접 생성

    Stream<String> stream = Stream.of("apple", "banana", "cherry");
    
    • Stream.of() 메서드로 임의의 개수의 요소로부터 스트림 생성


  5. 범위 지정 생성

    IntStream rangeStream = IntStream.range(1, 5);    // 1, 2, 3, 4
    IntStream closedStream = IntStream.rangeClosed(1, 5);    // 1, 2, 3, 4, 5
    
    • IntStream, LongStreamrange() 또는 rangeClosed() 사용


  6. 무한 스트림 생성

    // "hello" 문자열이 무한히 생성되는 스트림 (5개로 제한)
    Stream<String> generatedStream = Stream.generate(() -> "hello").limit(5);
    
    // 1부터 시작해 2씩 더해지는 무한 스트림 (5개로 제한)
    Stream<Integer> iteratedStream = Stream.iterate(1, n -> n + 2).limit(5);
    
    • Stream.generate() 또는 Stream.iterate() 사용
    • limit()과 함께 사용해야 한다.

특화 스트림

  • Primitive Specialized Streams (프리미티브 특화 스트림)

  • 특화 스트림 종류

    • IntStream
    • LongStream
    • DoubleStream

  • Stream API는 기본적으로 객체를 다루지만, boxing과 unboxing으로 인해 성능이 저하될 수 있기 때문에 이러한 특화 스트림을 제공한다.

  • 즉, 성능을 위해 박싱 없이 원시 타입을 직접 다루는 것이다.

    • boxing/unboxing 오버헤드 발생

      Integer[] integerArr = {1, 2, 3, 4, 5};
      int sum1 = Arrays.stream(integerArr)    // Stream<Integer>
          .mapToInt(Integer::intValue)        // IntStream으로 변환 (언박싱)
          .sum();
      
    • boxing/unboxing 없음

      int[] intArr = {1, 2, 3, 4, 5};
      int sum2 = Arrays.stream(intArr).sum();  // 바로 계산
      

  • 관련 생성 메서드

    IntStream.range(1, 10);              // 1부터 9까지
    DoubleStream.of(1.2, 3.4, 5.6);      // double 값들을 직접 전달
    LongStream.iterate(0, n -> n + 2);   // 무한 스트림
    

  • 편리한 특화 스트림 메서드

    int[] numbers = {10, 20, 30, 40, 50};
    
    int sum = Arrays.stream(numbers).sum();                          
    OptionalDouble average = Arrays.stream(numbers).average();       
    OptionalInt max = Arrays.stream(numbers).max();                  
    OptionalInt min = Arrays.stream(numbers).min();    
    
    IntSummaryStatistics stats = Arrays.stream(numbers).summaryStatistics();
    
    • 합계, 평균, 최댓값, 최솟값
    • summaryStatistics()으로 한번에 구할 수도 있다.

특화 스트림.boxed( )

  • boxed( ) 메서드

    IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
    
    Stream<Integer> boxedStream = intStream.boxed();
    
    • 특화 스트림일반 스트림으로 변환하는 메서드

    • 특화 스트림(IntStream …)은 collect() 메서드가 없다. ⇒ 변환 후 처리

      int[] numbers = {1, 2, 3, 4, 5};
      
      List<Integer> list = Arrays.stream(numbers)
          .boxed()
          .collect(Collectors.toList());
      
      • IntStreamStream<Integer>
52스트림의 중간 연산에는 어떤 것들이 있으며 데이터를 어떻게 가공하나요?쉬움
스트림의 중간 연산은 스트림의 요소를 가공하여 새로운 스트림을 반환하는 작업입니다. 대표적으로 쓰이는 것들로 filter는 조건에 맞는 요소를 걸러내고, map은 각 요소를 다른 값이나 타입으로 변환하며, sorted는 요소를 정렬합니다. 이러한 중간 연산들은 메서드 체이닝을 통해 연결될 수 있으며, 최종 연산이 호출될 때까지 지연 평가됩니다.
상세 설명

Stream의 중간 연산이란?

  • 스트림 파이프라인중간 단계를 구성하는 연산
  • 항상 새로운 스트림을 반환하여 메서드 체이닝 가능
  • 중간 연산의 작업은 지연 평가되어 최종 연산 호출 시에만 실행된다.

주요 중간 연산

  1. filter(Predicate<T> predicate)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
    
    List<Integer> evenNumbers = numbers.stream()
        .filter(n -> n % 2 == 0)         // 짝수만 필터링
        .collect(Collectors.toList());   // [2, 4, 6]
    
    • 주어진 조건에 맞는 요소만을 포함하는 새로운 스트림 반환


  2. map(Function<T, R> mapper)

    List<String> words = Arrays.asList("apple", "banana", "cherry");
    
    List<Integer> lengths = words.stream()
        .map(String::length)             // 각 단어를 길이(Integer)로 변환
        .collect(Collectors.toList());   // [5, 6, 6]
    
    • 각 요소를 주어진 함수에 따라 다른 값으로 변환


  3. sorted() / sorted(Comparator<T> comparator)

    List<String> fruits = Arrays.asList("banana", "apple", "cherry");
    
    List<String> sortedFruits = fruits.stream()
        .sorted()                        // 알파벳 순서로 정렬
        .collect(Collectors.toList());   // ["apple", "banana", "cherry"]
    
    List<String> sortedByLength = fruits.stream()
        .sorted(Comparator.comparingInt(String::length))  // 문자열 길이 기준
        .collect(Collectors.toList());                    // ["apple", "cherry", "banana"]
    
    • 요소들을 순서 또는 주어진 비교자에 따라 정렬
    • 비교자는 Comparator, 람다식 등을 사용 가능


  4. distinct()

    List<Integer> duplicates = Arrays.asList(1, 2, 2, 3, 3, 3);
    
    List<Integer> distinctNumbers = duplicates.stream()
        .distinct()
        .collect(Collectors.toList());    // [1, 2, 3]
    
    • 중복된 요소를 제거하여 고유한 요소만 포함하는 스트림 반환


  5. flatMap(Function<T, Stream<R>> mapper)

    List<List<Integer>> nestedList = Arrays.asList(
        Arrays.asList(1, 2), Arrays.asList(3, 4)    // [[1, 2], [3, 4]]
    );
    
    List<Integer> flatList = nestedList.stream()
        .flatMap(Collection::stream)    // 각 리스트를 스트림으로, 이를 다시 하나 스트림으로 합침
        .collect(Collectors.toList());  // [1, 2, 3, 4]
    
    • 여러 상자를 모두 풀어서 하나의 큰 상자로 만드는 것
    • 각 요소를 스트림으로 변환한 후, 생성된 모든 스트림을 하나로 평면화하여 반환

    • 중요한 것은 flatMapStream을 요구한다는 것이다.

    • 주로 중첩된 구조를 풀 때 사용 (이중 배열 ⇒ 단일 배열)

    • map()flatMap()

      List<String> words = Arrays.asList("Hello", "World");
      
      List<String[]> mapped = words.stream()
          .map(word -> word.split(""))  // String을 String[]로 변환
          .collect(Collectors.toList());
      
      • 결과 (이중 배열)
        • [["H","e","l","l","o"], ["W","o","r","l","d"]]
      List<String> words = Arrays.asList("Hello", "World");
      
      List<String> flatMapped = words.stream()
          .flatMap(word -> Arrays.stream(word.split("")))  // String[]을 Stream<String>으로
          .collect(Collectors.toList());
      
      • 결과 (평면화)
        • ["H","e","l","l","o","W","o","r","l","d"]


  6. limit(long maxSize) / skip(long n)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
    List<Integer> limited = numbers.stream()
        .limit(5)
        .collect(Collectors.toList());    // [1, 2, 3, 4, 5]
    
    List<Integer> skipped = numbers.stream()
        .skip(5)
        .collect(Collectors.toList());    // [6, 7, 8, 9, 10]
    
    • limit
      • 스트림의 첫 번째부터 지정된 개수만큼의 요소만 포함
    • skip
      • 스트림의 첫 번째부터 지정된 개수만큼 요소를 건너뛰고 나머지 요소 포함


  7. peek(Consumer<T> action)

    List<String> result = words.stream()
        .filter(word -> word.length() > 3)
        .peek(word -> System.out.println("After filter: " + word))  // 중간 결과 확인
        .map(String::toUpperCase)
        .peek(word -> System.out.println("After map: " + word))
        .collect(Collectors.toList());
    
    • 각 요소에 대해 지정된 동작을 수행하되, 스트림의 요소는 변경하지 않음
    • 주로 디버깅이나 로깅 목적으로 사용
    • 앞서 계속 언급했지만, 최종 연산 실행 전에 peek()도 마찬가지로 실행 안된다.

takeWhile( ) / dropWhile( )

  • filter() 연산은 모든 요소를 다 검사하지만 이것은 다르다.


  • takeWhile(Predicate predicate)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 6, 4, 5, 7, 8);
    
    List<Integer> result = numbers.stream()
        .takeWhile(n -> n < 5)           // 5보다 작은 동안만 계속
        .collect(Collectors.toList());   // [1, 2, 3] (6에서 멈춤!)
    
    • 조건이 false가 되는 순간까지만 가져오기

  • dropWhile(Predicate predicate)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 6, 4, 5, 7, 8);
    
    List<Integer> result = numbers.stream()
        .dropWhile(n -> n < 5)          // 5보다 작은 동안 버리기
        .collect(Collectors.toList());  // [6, 4, 5, 7, 8] (6부터 시작!)
    
    • 조건이 false가 되는 순간까지 다 버리기

mapToXXX

  • mapToInt, mapToLong, mapToDouble

    words.stream()
        .mapToInt(String::length)
        .sum();
    
    • 특화 스트림으로 변환
    • 특화 스트림으로 변환하는 이유는 박싱/언박싱 오버헤드를 줄이기 위함이다.

      List<String> words = Arrays.asList("apple", "banana", "cherry");
      
      int totalLength1 = words.stream()
          .map(String::length)         // Stream<Integer> (박싱)
          .mapToInt(Integer::intValue) // IntStream으로 변환 (언박싱)
          .sum();
      
      int totalLength2 = words.stream()
          .mapToInt(String::length)   // 바로 IntStream
          .sum();
      

Comparator

  • 메서드 참조 사용 (추천)

    employees.stream()
        .sorted(Comparator.comparing(Employee::getName))         // 이름순
        .sorted(Comparator.comparingInt(Employee::getAge))       // 나이순
        .sorted(Comparator.comparingDouble(Employee::getSalary)) // 급여순
        .collect(Collectors.toList());
    

  • 역순 정렬 (.reversed())

    employees.stream()
        .sorted(Comparator.comparing(Employee::getSalary).reversed())
        .collect(Collectors.toList());
    

  • 다중 조건 정렬 (.reversed.Comparing())

    employees.stream()
        .sorted(Comparator.comparing(Employee::getDepartment)    // 1차: 부서명
                        .thenComparing(Employee::getName))      // 2차: 이름
        .collect(Collectors.toList());
    

  • null 안전 정렬 (Comparator.nullsLast())

    employees.stream()
        .sorted(Comparator.comparing(
            Employee::getName, 
            Comparator.nullsLast(String::compareTo)
        )).collect(Collectors.toList());
    

주요 중간 연산

employees.stream()
    .sorted((a, b) -> a.getName().compareTo(b.getName()))
    .sorted((a, b) -> Double.compare(b.getSalary(), a.getSalary()))
    .collect(Collectors.toList());
53스트림의 최종 연산은 무엇이며, 가공된 데이터로부터 결과를 어떻게 얻나요?보통
최종 연산은 스트림 파이프라인을 실행시키고 최종 결과를 도출하는 연산입니다. 최종 연산이 호출되어야 모든 중간 연산이 해당 시점에 동작합니다. collect는 요소를 리스트, 맵 등 다양한 컬렉션으로 수집하고, reduce는 모든 요소를 하나의 값으로 집계하며, forEach는 각 요소를 활용하여 특정 작업을 수행합니다.
상세 설명

스트림의 최종 연산이란?

  • 스트림 파이프라인마지막 단계를 담당하는 연산
  • 스트림이 아닌 구체적인 결과값을 반환 (void 포함)
  • 최종 연산이 호출되어야 지연된 중간 연산들이 실제로 실행된다.

collect( )

  • collect(Collector<T, A, R> collector)

    • 스트림의 요소들을 수집하여 컬렉션이나 다른 형태로 변환

  1. 리스트로 수집

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Anna");
    
    List<String> nameList = names.stream().collect(Collectors.toList());
    
    • Collectors.toList()


  2. 으로 수집

    Map<String, Integer> nameLengthMap = names.stream()
        .collect(Collectors.toMap(name -> name, String::length));
    
    • Collectors.toMap(name -> name, String::length)
    • 이름 ⇒ 이름의 길이


  3. 그룹핑

    Map<Character, List<String>> groupedByName = names.stream()
        .collect(Collectors.groupingBy(name -> name.charAt(0)));
    
    // {A=[Alice, Anna], B=[Bob], C=[Charlie]}
    
    • Collectors.groupingBy(name -> name.charAt(0))
    • 이름의 첫 글자로 그룹화


  4. 문자열로 결합

    String joinedNames = names.stream().collect(Collectors.joining(", "));
    
    // "Alice, Bob, Charlie, Anna"
    
    • Collectors.joining(", ")

reduce( )

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

    • 모든 스트림 요소를 하나의 값으로 집계하는 연산
    • 초기값(identity)과 두 요소를 하나로 합치는 로직(BinaryOperator)을 제공

  1. reduce(BinaryOperator<T> accumulator) - Optional 반환

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    
    // 최댓값
    Optional<Integer> max = numbers.stream()
        .reduce(Integer::max);                    // Optional[5]
    
    // 최솟값
    Optional<Integer> min = numbers.stream()
        .reduce(Integer::min);                    // Optional[1]
    
    // 곱하기
    Optional<Integer> product = numbers.stream()
        .reduce((a, b) -> a * b);                 // Optional[120]
    
    // 빈 스트림의 경우
    Optional<Integer> empty = Arrays.<Integer>asList().stream()
        .reduce(Integer::sum);                    // Optional.empty()
    
    • Optional 반환 ⇒ 빈 스트림 가능성

  2. reduce(T identity, BinaryOperator<T> accumulator) - 값 직접 반환

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    
    // 합계 (초기값 0)
    int sum = numbers.stream()
        .reduce(0, Integer::sum);                 // 15
    
    // 곱하기 (초기값 1)
    int product = numbers.stream()
        .reduce(1, (a, b) -> a * b);              // 120
    
    // 문자열 연결
    List<String> words = Arrays.asList("Hello", "World", "Java");
    String result = words.stream()
        .reduce("", (a, b) -> a + " " + b);       // " Hello World Java"
    
    • 초기값을 함께 넣어서 안전하다.

  3. reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)

    List<String> words = Arrays.asList("apple", "banana", "cherry");
    
    // 모든 문자열 길이의 합 (타입 변환)
    int totalLength = words.stream()
        .reduce(
            0,                                    // 초기값 (Integer)
            (sum, word) -> sum + word.length(),   // 누적 함수 (Integer, String) -> Integer
            Integer::sum                          // 결합 함수 (병렬 처리용)
        );                                        // 17
    
    • 이종 타입 + 병렬 처리용


  • 실무 예시
    1. 복잡한 객체 집계

      class Order {
          private double amount;
          private String status;
          // getters...
      }
      
      List<Order> orders = getOrders();
      
      // 완료된 주문의 총 금액
      double totalCompletedAmount = orders.stream()
          .filter(order -> "COMPLETED".equals(order.getStatus()))
          .reduce(0.0, 
                (sum, order) -> sum + order.getAmount(),
                Double::sum);
      

    2. 문자열 조합

      List<String> names = Arrays.asList("김철수", "이영희", "박민수");
      
      String nameList = names.stream()
          .reduce("참석자: ", (result, name) -> result + name + ", ");
      
      • "참석자: 김철수, 이영희, 박민수, "

    3. 최고 급여 직원 찾기

      Optional<Employee> highestPaid = employees.stream()
          .reduce((emp1, emp2) -> 
              emp1.getSalary() > emp2.getSalary() ? emp1 : emp2)
      

    4. 조건부 집계

      int evenSum = numbers.stream()
          .filter(n -> n % 2 == 0)
          .reduce(0, Integer::sum);
      

forEach( )

  • forEach(Consumer<T> action)

    • 스트림의 각 요소에 대해 지정된 동작을 수행
    • 반환 값이 없으며(void), 주로 결과를 출력할 때 사용

  1. Stream forEach vs Collection forEach

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    
    // 컬렉션의 forEach
    names.forEach(System.out::println);   // 순서 보장
    
    // 스트림의 forEach  
    names.stream().forEach(System.out::println);   // 순서 보장
    
    // 병렬 스트림의 forEach
    names.parallelStream().forEach(System.out::println);   // 순서 보장 안됨!
    

  2. forEach vs forEachOrdered

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
    // 병렬 처리 - 순서 무작위
    numbers.parallelStream()
        .filter(n -> n > 5)
        .forEach(System.out::println);        // 출력: 6, 8, 7, 10, 9 (순서 보장 X)
    
    // 병렬 처리 - 순서 보장
    numbers.parallelStream()
        .filter(n -> n > 5)
        .forEachOrdered(System.out::println); // 출력: 6, 7, 8, 9, 10 (순서 보장)
    
    • 병렬 처리에서 중요


  • 실무 예시
    1. 데이터 검증 및 로깅

      List<User> users = getUsers();
      
      users.stream()
          .filter(user -> user.getAge() < 18)
          .forEach(user -> {
              log.warn("미성년자 사용자 발견: {}", user.getName());
              // 별도 처리 로직
              sendNotificationToParent(user);
          });
      

    2. 파일 쓰기

      List<String> logs = getLogMessages();
      
      try (PrintWriter writer = new PrintWriter("output.txt")) {
          logs.stream()
              .filter(log -> log.contains("ERROR"))
              .forEach(writer::println);
      }
      

    3. 이메일 발송

      List<Customer> customers = getVipCustomers();
      
      customers.stream()
          .filter(customer -> customer.hasUnreadPromotions())
          .forEach(customer -> {
              emailService.sendPromotionEmail(customer.getEmail());
              customer.markPromotionAsSent();
          });
      

    4. 캐시 업데이트

      Map<String, Object> cache = new ConcurrentHashMap<>();
      
      processedData.stream()
          .forEach(data -> cache.put(data.getId(), data.getValue()));
      

조건 검사 연산 - xxxMatch( )

  • 조건 검사 연산

    List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
    
    boolean hasOdd = numbers.stream()
        .anyMatch(n -> n % 2 != 0);    // 홀수가 하나라도 있나?  => false
        
    boolean allEven = numbers.stream()
        .allMatch(n -> n % 2 == 0);    // 모두 짝수?  => true
        
    boolean noOdd = numbers.stream()
        .noneMatch(n -> n % 2 != 0);   // 홀수가 하나도 없나?  => true
    
    • 주어진 조건을 스트림의 요소들이 만족하는지 여부를 확인
    • 종류
      • anyMatch
      • allMatch
      • noneMatch

검색 및 계산 연산

  1. count()

    long count = numbers.stream().count();   // 5
    
    • 요소의 개수 세기

  2. findFirst()

    Optional<Integer> firstElement = numbers.stream().findFirst();   // Optional[2]
    
    • 첫 번째 요소를 찾는다.
    • 결과는 Optional<T>로 반환

  3. findAny()

    Optional<Integer> anyElement = numbers.stream().findAny();
    
    • 임의의 요소를 찾음 (병렬 스트림에서 유용)

  4. min() / max()

    Optional<Integer> min = numbers.stream().min(Integer::compareTo);
    Optional<Integer> max = numbers.stream().max(Integer::compareTo);
    
    • 최솟값, 최댓값
54스트림으로 복잡한 데이터를 그룹핑하고, 대용량 데이터 처리 성능을 높이려면 어떻게 해야 하나요?어려움
답변 작성 예정
상세 설명

고급 컬렉터와 병렬 스트림

  • 내용이 추가될 예정입니다
55스트림 결과가 null일 때 안전하게 처리하고, 숫자 연산의 성능을 높이는 방법은 무엇인가요?보통
답변 작성 예정
상세 설명

Optional과 특화 스트림

  • 내용이 추가될 예정입니다