본문 바로가기
Java

[Java] 스트림(Stream) 특징, 사용 예제

by 하부루 2024. 10. 11.

Stream이란?

  • Java 8부터 Stream이 Java에 도입되었다.
  • Stream의 주 목적은 복잡한 데이터 처리 간소화, 코드 가독성 향상, 쉬운 병렬 처리 적용이다.
  • Stream의 사전적 의미는 흐르다이다. 어떠한 것이 흐르는 것이 아니고 데이터의 흐름을 말한다.

[스트림의 데이터 흐름 참고 사진]

 

[Stream의 데이터 흐름]

1. 위 그림은 어부가 물고기를 그물로 잡고, 여러 마리를 일정한 기준으로 모아서 상자에 넣고,
   이들을 하나로 모은 뒤 트럭에 실어서 운반하는 과정을 나타내고 있다.

2. 위 그림에서의 물고기와 같은 어류의 이동을 stream이라고 정의할 수 있다.

3. 어부가 어류 중에서도 고등어를 잡고 싶어서 그물로 고등어를 잡았다.
   이 행위를 filter라고 하고, 이 연산자를 중간 연산자라고 합니다.

4. 그리고 고등어를 포장하지 않고 생으로 팔 수는 없기 때문에 상자에 담아야 합니다.
   이 행위를 map이라고 하고, 이 연산자도 마찬가지로 중간 연산자라고 합니다.

5. 마지막으로, 고등어가 실린 수많은 상자를 운반하여 다른 곳으로 이동하면서 끝이 난다.
   이 행위를 collect라고 하고, 이 연선자는 최종 연산자라고 합니다.

6. 요약하자면, 스트림은 수 많은 데이터의 흐름 속에서 각각의 원하는 값을 가공하여
   최종 소비자에게 제공하는 역할을 한다고 보면 된다.

 

Stream 특징

  • Stream은 데이터 자체를 저장하지 않고, 데이터를 처리하는 파이프라인을 제공한다. map(), filter() 등을 사용해 데이터를 변환하고 필터링하는데에 유리하다.
  • Stream은 지연 평가 방식이다. 예를 들어, filter()와 같은 중간 연산을 여러개 정의하더라도 최종적으로 collect()와 같은 최종 연산이 호출될 때까지 연산은 실행되지 않는다. 이는 성능 최적화에 도움을 준다.
  • Stream은 불변성을 가지고있다. Stream은 원본 데이터를 변경 하지않고, 변환 후의 결과를 새로운 스트림으로 반환한다. 안전한 동시성을 보장한다.
  • 데이터 가공전의 Stream 자체는 재사용이 불가능하다.

 

Stream 사용 예제

public class StreamTest {

    public static void main(String[] args) {

        // 1. Stream 사용 전
        List<Person> people = new ArrayList<>();

        people.add(new Person("Kevin", 200));
        people.add(new Person("John", 140));
        people.add(new Person("Neko", 100));
        people.add(new Person("Dan", 60));

        List<Person> hundredClub = new ArrayList<>();

        for(Person p : people) {
            if(p.money >= 100) {
                hundredClub.add(p);
            }
        }
        System.out.println("======== person.name ========");
        hundredClub.forEach(person -> System.out.println(person.name));
    }

    static class Person {
        String name;
        int money;

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

결과 :

======== person.name ========
Kevin
John
Neko
public class StreamTest {

    public static void main(String[] args) {

        // 2. Stream 사용 후
        List<Person> people = new ArrayList<>();

        people.add(new Person("Kevin", 200));
        people.add(new Person("John", 140));
        people.add(new Person("Neko", 100));
        people.add(new Person("Dan", 60));

        // filter
        List<Person> hundredClub = people.stream()
                .filter(person -> person.money >= 100)
                .collect(Collectors.toList());

        System.out.println("======== filterList ========");
        hundredClub.forEach(person -> System.out.println(person.name));

        // sorted
        List<Person> sortedList = people.stream()
                .sorted(Comparator.comparing(person -> person.name))
                .collect(Collectors.toList());

        System.out.println("======== sortedList ========");
        sortedList.forEach(person -> System.out.println(person.name));

        // filter + sorted
        List<Person> hundredSortedClub = people.stream()
                .filter(person -> person.money >= 100)
                .sorted(Comparator.comparing(person -> person.name))
                .collect(Collectors.toList());

        System.out.println("======== filterList + sortedList ========");
        hundredSortedClub.forEach(person -> System.out.println(person.name));

        // map : Person의 이름만 추출하여 리스트로 변환
        List<String> names = people.stream()
                .map(person -> person.name)
                .collect(Collectors.toList());

        System.out.println("======== Person Names ========");
        names.forEach(System.out::println);

        // map 사용 예제 2: Person의 money를 10% 더해서 새로운 리스트로 생성
        List<Integer> updatedMoney = people.stream()
                .map(person -> person.money + (int)(person.money * 0.1))
                .collect(Collectors.toList());

        System.out.println("======== Updated Money (10% added) ========");
        updatedMoney.forEach(System.out::println);
    }

    static class Person {
        String name;
        int money;

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

결과 :

======== filterList ========
Kevin
John
Neko
======== sortedList ========
Dan
John
Kevin
Neko
======== filterList + sortedList ========
John
Kevin
Neko
======== Person Names ========
Kevin
John
Neko
Dan
======== Updated Money (10% added) ========
220
154
110
66

package com.example.test.Stream;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTest {

    public static void main(String[] args) {

        // 3. 데이터 가공 전의 Stream은 재사용 불가능
        List<Person> people = new ArrayList<>();

        people.add(new Person("Kevin", 200));
        people.add(new Person("John", 140));
        people.add(new Person("Neko", 100));
        people.add(new Person("Dan", 60));
        
        Stream<Person> personStream = people.stream();

        // 첫 번째 사용: 정상 동작
        personStream.filter(person -> person.money >= 100)
                .forEach(person -> System.out.println(person.name));

        // 두 번째 사용: 에러 발생 (스트림은 한 번만 사용 가능)
        personStream.forEach(person -> System.out.println(person.name));
    }

    static class Person {
        String name;
        int money;

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

결과 :

Exception in thread "main" java.lang.IllegalStateException:
stream has already been operated upon or closed