Post

Java Stream forEach 중단

1. Java 9의 Stream.takeWhile()

문자열 항목의 스트림이 있고 길이가 홀수인 한 해당 요소를 처리하려고 한다.

Java 9 Stream.takeWhile 메소드를 사용한다.

1
2
3
Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
  .takeWhile(n -> n.length() % 2 != 0)
  .forEach(System.out::println);

이것을 실행하면 다음과 같은 출력을 얻는다.

1
2
cat
dog

어떻게 작동하는지 알아보기 위해 for 루프와 break 문을 사용하여 일반 Java의 동등한 코드와 이를 비교한다.

1
2
3
4
5
6
7
8
List<String> list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck");
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if (item.length() % 2 == 0) {
        break;
    }
    System.out.println(item);
}

takeWhile 메소드를 사용하면 필요한 것을 정확하게 얻을 수 있다.

2. Custom Spliterator

Stream.spliteratordecorator로 작동할 사용자 정의 Spliterator를 만든다. 이 Spliterator가 break를 수행 하도록 할 수 있다.

먼저 스트림에서 Spliterator를 가져온 다음 이를 CustomSpliterator로 장식하고 break 작업을 제어하기 위한 조건자를 제공한다. 마지막으로 CustomSpliterator에서 새 스트림을 생성한다.

1
2
3
4
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
    CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
    return StreamSupport.stream(customSpliterator, false);
}

CustomSpliterator를 생성하는 방법을 살펴본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {

    private Spliterator<T> splitr;
    private Predicate<T> predicate;
    private boolean isMatched = true;

    public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
        super(splitr.estimateSize(), 0);
        this.splitr = splitr;
        this.predicate = predicate;
    }

    @Override
    public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
        boolean hadNext = splitr.tryAdvance(elem -> {
            if (predicate.test(elem) && isMatched) {
                consumer.accept(elem);
            } else {
                isMatched = false;
            }
        });
        return hadNext && isMatched;
    }
}

tryAdvance 메소드를 살펴본다. 여기서는 사용자 정의 Spliterator가 장식된 Spliterator의 요소를 처리하는 것을 볼 수 있다. 조건자가 일치하고 초기 스트림에 여전히 요소가 있는한 처리가 완료된다. 조건 중 하나가 false가 되면 Spliterator가 “breaks” 되고 스트리밍 작업이 종료된다.

새로운 helper 메서드를 테스트해 본다.

1
2
3
4
5
6
7
8
9
10
11
@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = 
      Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");

    List<String> result = 
      CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
        .collect(Collectors.toList());

    assertEquals(asList("cat", "dog"), result);
}

조건이 충족된 후 스트림이 중지되었다. 테스트 목적으로 결과를 목록으로 수집했지만 forEach 호출이나 Stream의 다른 기능을 사용할 수도 있다.

3. Custom forEach

break 메커니즘이 포함된 스트림을 제공하는 것이 유용할 수 있지만 forEach 작업에만 집중하는 것이 더 간단할 수 있다.

데코레이터 없이 Stream.spliterator를 직접 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CustomForEach {

    public static class Breaker {
        private boolean shouldBreak = false;

        public void stop() {
            shouldBreak = true;
        }

        boolean get() {
            return shouldBreak;
        }
    }

    public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {
        Spliterator<T> spliterator = stream.spliterator();
        boolean hadNext = true;
        Breaker breaker = new Breaker();

        while (hadNext && !breaker.get()) {
            hadNext = spliterator.tryAdvance(elem -> {
                consumer.accept(elem, breaker);
            });
        }
    }
}

새로운 사용자 정의 forEach 메소드는 코드에 스트림을 중지하는데 사용할 수 있는 다음 요소와 차단기 객체를 모두 제공하는 BiConsumer를 호출한다.

단위 테스트에서 이것을 시험해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
    List<String> result = new ArrayList<>();

    CustomForEach.forEach(initialStream, (elem, breaker) -> {
        if (elem.length() % 2 == 0) {
            breaker.stop();
        } else {
            result.add(elem);
        }
    });

    assertEquals(asList("cat", "dog"), result);
}

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.