[Java] Stream API

wujin·2023년 7월 16일
0

Stream API

Java 8부터 도입된 Stream API는 데이터의 시퀀스를 처리하기 위한 일련의 요소들의 연속적인 흐름이다.
Stream은 컬렉션, 배열 또는 I/O 자원과 같은 데이터 소스에서 데이터를 추상화하고, 데이터를 다루는 다양한 기능을 제공한다.
Stream은 데이터 소스로부터 데이터를 읽고, 변환하고, 조작하며, 결과를 수집할 수 있는 기능을 제공한다.

Stream API 특징

  • 스트림은 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(internal iteration)을 통해 작업을 수행한다.

  • 스트림은 재사용이 가능한 컬렉션과는 달리 단 한 번만 사용할 수 있다.

  • 스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.

  • 스트림은 데이터 소스에 대한 원본 데이터를 변경하지 않는다. 대신, 스트림은 요소들의 연속적인 흐름을 통해 변환된 데이터를 제공한다.

  • 스트림은 parallelStream() 메소드를 통한 손쉬운 병렬 처리를 지원한다.

  • 스트림은 병렬 처리를 위한 기능을 제공한다. 이는 멀티스레딩을 사용하여 데이터를 동시에 처리하고, 성능을 향상시킬 수 있다.

Stream API 동작 흐름

Stream API는 다음과 같이 세 가지 단계에 걸쳐서 동작한다.

  1. 스트림의 생성
  2. 스트림의 중개 연산 (스트림의 변환)
  3. 스트림의 최종 연산 (스트림의 사용)


Stream의 생성

스트림은 다양한 데이터 소스로부터 생성할 수 있다.

  1. 컬렉션
  2. 배열
  3. 가변 매개변수
  4. 지정된 범위의 연속된 정수
  5. 특정 타입의 난수들
  6. 람다 표현식
  7. 파일
  8. 빈 스트림

컬렉션에서 스트림 생성

List<String> names = Arrays.asList("John", "Alice", "Bob", "David");

Stream<String> stream = names.stream();

배열에서 스트림 생성

int[] numbers = {1, 2, 3, 4, 5};

IntStream stream = Arrays.stream(numbers);

정적 팩토리 메서드를 사용한 스트림 생성

Stream<String> stream = Stream.of("apple", "banana", "cherry");

스트림의 중개 연산 (스트림의 변환)

스트림 API에 의해 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환된다.
이러한 중개 연산은 스트림을 전달받아 스트림을 반환하므로, 중개 연산은 연속으로 연결해서 사용할 수 있다.
또한, 스트림의 중개 연산은 필터-맵(filter-map) 기반의 API를 사용함으로 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.

스트림 API에서 사용할 수 있는 대표적인 중개 연산과 그에 따른 메소드는 아래와 같다.

  1. 스트림 필터링 : filter(), distinct()
  2. 스트림 변환 : map(), flatMap()
  3. 스트림 제한 : limit(), skip()
  4. 스트림 정렬 : sorted()
  5. 스트림 연산 결과 확인 : peek()

Mapping(매핑) 예시

List<String> names = Arrays.asList("John", "Alice", "Bob", "David");

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

System.out.println(nameLengths); // 출력: [4, 5, 3, 5]

Filtering(필터링) 예시

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

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

System.out.println(evenNumbers); // 출력: [2, 4]

스트림의 최종 연산 (스트림의 사용)

스트림 API에서 중개 연산을 통해 변환된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시한다.
즉, 지연(lazy)되었던 모든 중개 연산들이 최종 연산 시에 모두 수행되는 것이다.
이렇게 최종 연산 시에 모든 요소를 소모한 해당 스트림은 더는 사용할 수 없게 된다.

스트림 API에서 사용할 수 있는 대표적인 최종 연산과 그에 따른 메소드는 다음과 같다.

  1. 요소의 출력 : forEach()
  2. 요소의 소모 : reduce()
  3. 요소의 검색 : findFirst(), findAny()
  4. 요소의 검사 : anyMatch(), allMatch(), noneMatch()
  5. 요소의 통계 : count(), min(), max()
  6. 요소의 연산 : sum(), average()
  7. 요소의 수집 : collect()

요소 수 세기

List<String> names = Arrays.asList("John", "Alice", "Bob", "David");

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

System.out.println(count); // 출력: 4

요소 수집

List<String> names = Arrays.asList("John", "Alice", "Bob", "David");

String joinedNames = names.stream()
                          .collect(Collectors.joining(", "));

System.out.println(joinedNames); // 출력: John, Alice, Bob, David

요소 검색

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

Optional<Integer> result = numbers.stream()
                                  .filter(n -> n > 3)
                                  .findFirst();

result.ifPresent(System.out::println); // 출력: 4

예시

1. 정수 리스트에서 짝수의 합 구하기

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sumOfEvenNumbers = numbers.stream()
                              .filter(n -> n % 2 == 0)
                              .mapToInt(Integer::intValue)
                              .sum();

System.out.println(sumOfEvenNumbers); // 출력: 30

2. 문자열 리스트에서 길이가 5 이상인 단어 필터링하기

List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant");

List<String> filteredWords = words.stream()
                                 .filter(word -> word.length() >= 5)
                                 .collect(Collectors.toList());

System.out.println(filteredWords); // 출력: [apple, banana, elephant]

3. 객체 리스트에서 특정 조건을 만족하는 요소 찾기

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

List<Person> people = Arrays.asList(
        new Person("John", 25),
        new Person("Alice", 30),
        new Person("Bob", 20)
);

Optional<Person> personOptional = people.stream()
                                        .filter(person -> person.getAge() > 25)
                                        .findFirst();

personOptional.ifPresent(person -> System.out.println(person.getName())); // 출력: Alice

0개의 댓글