Stream

백기선 님의 <더 자바, Java 8>를 보고 공부한 내용을 기록합니다.

1. 개념

  • 데이터를 담고 있는 저장소(컬렉션)이 아니다. 주의할것!

  • Functional in nature - 스트림이 처리하는 데이터소스를 변경하지 않는다.

  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다.

  • 무제한일수도 있다. - short circuit 메소드를 사용해서 제한할 수 있다.

  • 중개 오퍼레이션은 근본적으로 lazy 하다.

    • 중개 오퍼레이션 : 중간에서 이어준다.

    • 종료(터미널) 오퍼레이션 : 종료

  • 손쉽게 병렬처리를 할 수 있다.

    • parallelStream() 이후에 주는 오퍼레이터들은 모두 병렬적으로 처리된다.

    • 샛길

      • 병렬처리가 항상 빠른 것은 아니다.

      • 데이터가 엄청나게 많은 경우, 유용하게 사용될 수 있다.

      • 어찌되었든 병렬처리는 쓰레드를 하나 더 만드는 비용이 들기 때문에 주의해야한다.

2. stream pipeline

  • 0 또는 다수의 중개 오퍼레이션 (intermediate operation)과 한 개의 종룔 오퍼레이션(terminal operation) 으로 구성된다.

  • 스트림의 데이터 소스는 오직 터미널 오퍼레이션을 실행할 때에만 처리한다.

3. 중개 오퍼레이션

  • Stream 을 리턴한다.

  • Stateless / Stateful 오퍼레이션으로 상세하게 구분할 수도 있다.

    • 대부분은 stateless

    • distinct, sorted 처럼 이전 소스 데이터를 참조해야할 경우 stateful

  • 예시

    • filter, map, limit, skip, sorted ...

4. 종료 오퍼레이션

  • Stream 을 리턴하지 않는다.

  • 다양한 종류

    • collect, allMatch, count, forEach, min, max ..

5. 참고

6. 코드로 확인하기

6-1. 걸러내기

  • Filter(Predicate)

6-2. 변경하기

  • Map(Function) 또는 FlatMap(Function)

  • 예) 각각의 Post 인스턴스에서 String title만 새로운 스트림으로

  • 예) List<Stream<String>>을 String의 스트림으로

6-3. 생성하기

  • generate(Supplier) 또는 Iterate(T seed, UnaryOperator)

  • 예) 10부터 1씩 증가하는 무제한 숫자 스트림

  • 예) 랜덤 int 무제한 스트림

6-4. 제한하기

  • limit(long) 또는 skip(long)

  • 예) 최대 5개의 요소가 담긴 스트림을 리턴한다.

  • 예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

6-5. 스트림에 있는 데이터가 특정 조건을 만족하는지 확인

  • anyMatch(), allMatch(), nonMatch()

  • 예) k로 시작하는 문자열이 있는지 확인한다. (true 또는 false를 리턴한다.)

  • 예) 스트림에 있는 모든 값이 10보다 작은지 확인한다.

6-6. 개수 세기

  • count()

  • 예)10보다 큰 수의 개수를 센다.

6-7. 스트림을 데이터 하나로 뭉치기

  • reduce(identity, BiFunction), collect(), sum(), max()

  • 예)모든 숫자 합 구하기

  • 예)모든 데이터를 하나의 List 또는 Set에 옮겨담기


public class StreamTest {

    private List<Food> asianFoods;
    private List<Food> westernFoods;

    @BeforeEach
    void init() {
        asianFoods = new ArrayList<>(){{
            add(new Food(1, "비빔밥", true));
            add(new Food(2, "설렁탕", false));
            add(new Food(3, "불닭볶음면", true));
            add(new Food(4, "떡볶이", true));
            add(new Food(5, "궁중떡볶이", false));
            add(new Food(6, "순대국", false));
        }};

        westernFoods = new ArrayList<>(){{
            add(new Food(1, "토마토 파스타", true));
            add(new Food(2, "목살 스테이크", true));
            add(new Food(3, "티본 스테이크", false));
            add(new Food(4, "미국식 짠 피자", false));
        }};
    }

    @Test
    void 떡볶이_메뉴만() {
        List<Food> filtered = asianFoods.stream().filter(s -> s.getName().contains("떡볶이")).collect(Collectors.toList());

        assertThat(filtered).hasSize(2);
        assertThat(filtered).containsAll(
                List.of(new Food(4, "떡볶이", true),
                        new Food(5, "궁중떡볶이", false)));
    }

    @Test
    void 좋아하지_않는_음식만() {
        List<Food> filtered = asianFoods.stream().filter(f -> !f.isLiked()).collect(Collectors.toList());

        assertThat(filtered).hasSize(3);
        assertThat(filtered).containsAll(List.of(
                new Food(2, "설렁탕", false),
                new Food(5, "궁중떡볶이", false),
                new Food(6, "순대국", false)
        ));
    }

    @Test
    void 음식이름만_모아서_스트림_만들기() {
        List<String> foodNames = asianFoods.stream().map(Food::getName).collect(Collectors.toList());

        assertThat(foodNames).hasSize(6);
        assertThat(foodNames).containsAll(List.of("비빔밥", "설렁탕", "불닭볶음면", "떡볶이", "궁중떡볶이", "순대국"));
    }

    @Test
    void 아시아와_서양음식의_모든_이름만_출력하기() {
        List<List<Food>> globalFoods = new ArrayList<>(){{
            add(asianFoods);
            add(westernFoods);
        }};


        // with lambda : globalFoods.stream().flatMap(Collection::stream).map(Food::getName).collect(Collectors.toList());
        List<String> foodNames = globalFoods.stream()
                .flatMap(foods -> foods.stream())
                .map(food -> food.getName())
                .collect(Collectors.toList());

        assertThat(foodNames).isEqualTo(List.of(
                "비빔밥", "설렁탕", "불닭볶음면", "떡볶이", "궁중떡볶이", "순대국", "토마토 파스타", "목살 스테이크", "티본 스테이크", "미국식 짠 피자"
        ));
    }

    @Test
    void _10부터_1씩_증가하는_무제한스트림에서_처음의_10개_제외하고_최대_10개까지만_출력() {
        List<Integer> ints = Stream.iterate(10, i -> i + 1)
                .skip(10)
                .limit(10)
                .collect(Collectors.toList());

        assertThat(ints).isEqualTo(List.of(20, 21, 22, 23, 24, 25, 26, 27, 28, 29));
    }

    @Test
    void 서양음식중에서_피자종류가_있는지_확인() {
        boolean hasPizza = westernFoods.stream()
                .anyMatch(f -> f.getName().contains("피자"));

        assertThat(hasPizza).isTrue();
    }
}

Last updated