스트림(Stream)

스트림이란?

자바 8부터 도입된 스트림(Stream)은 데이터를 처리하는 연속적인 연산을 지원하는 기능입니다. 스트림을 사용하면 컬렉션, 배열 또는 I/O 자원과 같은 데이터 소스를 함수형 스타일로 다룰 수 있습니다.

https://www.ifourtechnolab.com/blog/a-guide-on-java-stream-api

특징

  • 연속된 연산: 스트림은 데이터를 처리하는 일련의 연속된 연산을 수행합니다. 이러한 연산은 중간 연산과 최종 연산으로 구분됩니다. 중간 연산은 다른 스트림을 반환하며, 최종 연산은 결과를 반환하거나, 외부 자원에 작업을 수행하거나, 최종 결과를 도출합니다.
  • 지연 연산: 스트림은 지연 연산을 지원합니다. 즉, 중간 연산은 실제로 수행되지 않고 최종 연산이 호출될 때까지 기다립니다. 이를 통해 최적화된 실행 계획을 구성할 수 있습니다.
  • 파이프라인: 스트림은 연속된 연산들로 구성된 파이프라인이라고 생각할 수 있습니다. 데이터는 연산들을 통과하면서 필터링, 매핑, 정렬, 그룹화 등의 작업을 수행합니다.
컴퓨터 용어에서 "파이프라인"은 작업을 연속적으로 처리하기 위해 데이터나 명령어의 흐름을 구성하는 개념을 말합니다. 이 용어는 실제로 파이프라인이라는 파이프를 통해 물이나 기체가 흐르는 과정을 연상시키는데서 유래하였습니다.

예시

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> evenSquares = numbers.stream()
                .filter(n -> n % 2 == 0)       // 중간 연산: 짝수만 선택
                .map(n -> n * n)              // 중간 연산: 제곱
                .collect(Collectors.toList());  // 최종 연산: 리스트로 변환

        System.out.println(evenSquares);    // 출력: [4, 16, 36, 64, 100]
    }
}

중간 연산(Intermediate Operations)

중간 연산은 스트림을 변환하거나 필터링하는 연산입니다. 중간 연산은 다른 스트림을 반환하며, 여러 개의 중간 연산을 연결하여 파이프라인을 형성할 수 있습니다. 중간 연산은 스트림의 요소에 대한 처리를 지연시키고, 최종 연산이 호출될 때까지 출력되지 않습니다.

 

메소드

  • filter(Predicate): 주어진 조건을 만족하는 요소만 선택합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evenNumbers = numbers.stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());

System.out.println(evenNumbers); // 출력: [2, 4, 6, 8, 10]
  • map(Function): 각 요소를 변환하여 새로운 값으로 매핑합니다.
List<String> names = Arrays.asList("John", "Jane", "Alice", "Bob");

List<Integer> nameLengths = names.stream()
        .map(String::length)
        .collect(Collectors.toList());

System.out.println(nameLengths); // 출력: [4, 4, 5, 3]
  • sorted(): 요소를 정렬합니다.
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

List<Integer> sortedNumbers = numbers.stream()
        .sorted()
        .collect(Collectors.toList());

System.out.println(sortedNumbers); // 출력: [1, 3, 5, 7, 9]
  • distinct(): 중복 요소를 제거합니다.
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5, 5);

List<Integer> distinctNumbers = numbers.stream()
        .distinct()
        .collect(Collectors.toList());

System.out.println(distinctNumbers); // 출력: [1, 2, 3, 4, 5]
  • limit(long): 지정된 개수까지의 요소만 선택합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> limitedNumbers = numbers.stream()
        .limit(5)
        .collect(Collectors.toList());

System.out.println(limitedNumbers); // 출력: [1, 2, 3, 4, 5]
  • skip(long): 지정된 개수의 요소를 건너뛰고 나머지 요소를 선택합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> skippedNumbers = numbers.stream()
        .skip(3)
        .collect(Collectors.toList());

System.out.println(skippedNumbers); // 출력: [4, 5, 6, 7, 8, 9, 10]
  • flatMap(Function): 각 요소를 다른 스트림으로 매핑한 후, 모든 스트림을 하나의 스트림으로 평면화합니다.
List<List<Integer>> numberLists = Arrays.asList(
        Arrays.asList(1, 2),
        Arrays.asList(3, 4),
        Arrays.asList(5, 6)
);

List<Integer> flattenedNumbers = numberLists.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());

System.out.println(flattenedNumbers); // 출력: [1, 2, 3, 4, 5, 6]
  • peek(Consumer): 각 요소를 소비하고 추가적인 작업을 수행합니다. (주로 디버깅 목적으로 사용될 수 있습니다.)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> doubledNumbers = numbers.stream()
        .peek(n -> System.out.println("Processing number: " + n))
        .map(n -> n * 2)
        .collect(Collectors.toList());

System.out.println(doubledNumbers); // 출력: [2, 4, 6, 8, 10]
  • sorted(Comparator): 요소를 주어진 비교자(Comparator)를 기준으로 정렬합니다.
List<String> names = Arrays.asList("John", "Alice", "Bob", "David");

List<String> sortedNames = names.stream()
        .sorted(Comparator.comparing(String::length))
        .collect(Collectors.toList());

System.out.println(sortedNames); // 출력: [Bob, John, Alice, David]
  • filter와 map의 조합: filter와 map을 함께 사용하여 요소를 선택하고 변환할 수도 있습니다.
List<String> names = Arrays.asList("John", "Jane", "Alice", "Bob");

List<String> filteredAndMappedNames = names.stream()
        .filter(name -> name.startsWith("J"))
        .map(String::toUpperCase)
        .collect(Collectors.toList());

System.out.println(filteredAndMappedNames); // 출력: [JOHN, JANE]

최종 연산Terminal Operation

최종 연산(Terminal Operation)은 스트림의 요소를 소모하고 최종 결과를 반환하는 연산입니다. 여기에는 다양한 메서드가 있습니다. 아래에서 몇 가지를 소개하고, 각각에 대한 설명과 예시 코드를 제공하겠습니다.

 

메소드

  • forEach(Consumer): 각 요소를 소비하여 처리합니다.
List<String> names = Arrays.asList("John", "Jane", "Alice", "Bob");

names.stream()
    .forEach(System.out::println);
// 출력:
// John
// Jane
// Alice
// Bob
  • count(): 스트림의 요소 수를 반환합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

long count = numbers.stream()
    .count();

System.out.println(count); // 출력: 5
  • collect(Collector): 요소를 수집하여 컬렉션 또는 다른 데이터 구조로 변환합니다.
List<String> names = Arrays.asList("John", "Jane", "Alice", "Bob");

Set<String> nameSet = names.stream()
    .collect(Collectors.toSet());

System.out.println(nameSet); // 출력: [John, Jane, Alice, Bob]
  • min(Comparator)와 max(Comparator): 최솟값 또는 최댓값을 반환합니다.
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

Optional<Integer> minNumber = numbers.stream()
    .min(Comparator.naturalOrder());

System.out.println(minNumber.orElse(0)); // 출력: 1

Optional<Integer> maxNumber = numbers.stream()
    .max(Comparator.naturalOrder());

System.out.println(maxNumber.orElse(0)); // 출력: 9
  • reduce(BinaryOperator): 모든 요소를 하나의 값으로 축소합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b);

System.out.println(sum.orElse(0)); // 출력: 15
  • anyMatch(Predicate): 하나 이상의 요소가 주어진 조건을 만족하는지 확인합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean hasEvenNumber = numbers.stream()
    .anyMatch(n -> n % 2 == 0);

System.out.println(hasEvenNumber); // 출력: true
  • allMatch(Predicate): 모든 요소가 주어진 조건을 만족하는지 확인합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean allGreaterThanZero = numbers.stream()
    .allMatch(n -> n > 0);

System.out.println(allGreaterThanZero); // 출력: true
  • noneMatch(Predicate): 모든 요소가 주어진 조건을 만족하지 않는지 확인합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean noneNegative = numbers.stream()
    .noneMatch(n -> n < 0);

System.out.println(noneNegative); // 출력: true
  • findFirst(): 첫 번째 요소를 반환합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> firstNumber = numbers.stream()
    .findFirst();

System.out.println(firstNumber.orElse(0)); // 출력: 1
  • findAny(): 임의의 요소를 반환합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> anyNumber = numbers.stream()
    .findAny();

System.out.println(anyNumber.orElse(0)); // 출력: 1 또는 2 또는 3 ... (임의의 요소)
  • toArray(): 스트림의 요소를 배열로 변환합니다.
List<String> names = Arrays.asList("John", "Jane", "Alice", "Bob");

String[] nameArray = names.stream()
    .toArray(String[]::new);

System.out.println(Arrays.toString(nameArray)); // 출력: [John, Jane, Alice, Bob]
  • sum(): 숫자 요소의 합계를 반환합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum();

System.out.println(sum); // 출력: 15
  • average(): 숫자 요소의 평균을 반환합니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

OptionalDouble average = numbers.stream()
    .mapToDouble(Integer::doubleValue)
    .average();

System.out.println(average.orElse(0)); // 출력: 3.0
  • groupingBy(): 지정된 기준에 따라 요소를 그룹화하여 맵으로 반환합니다.
List<Person> people = Arrays.asList(
    new Person("John", 25),
    new Person("Jane", 30),
    new Person("Alice", 25),
    new Person("Bob", 35)
);

Map<Integer, List<Person>> ageGroups = people.stream()
    .collect(Collectors.groupingBy(Person::getAge));

System.out.println(ageGroups);
// 출력: {25=[Person{name='John', age=25}, Person{name='Alice', age=25}],
//        30=[Person{name='Jane', age=30}],
//        35=[Person{name='Bob', age=35}]}
  • toMap(): 지정된 키와 값 매핑에 따라 요소를 맵으로 반환합니다.
List<Person> people = Arrays.asList(
    new Person("John", 25),
    new Person("Jane", 30),
    new Person("Alice", 25),
    new Person("Bob", 35)
);

Map<String, Integer> nameAgeMap = people.stream()
    .collect(Collectors.toMap(Person::getName, Person::getAge));

System.out.println(nameAgeMap);
// 출력: {John=25, Jane=30, Alice=25, Bob=35}

 

'Language > Java' 카테고리의 다른 글

Enum(열거형) 클래스  (0) 2023.06.02
Optional<T>  (0) 2023.05.18
리플렉션(Reflection)  (0) 2023.05.11
제네릭스<Generics>  (0) 2023.05.03
Collections Framework  (0) 2023.04.26