Ngắt vòng lặp forEach trong Java Stream

Stream trong Java cung cấp forEach method cho phép duyệt từng phần tử trong Stream. Mặc dù nó khá giống với vòng lặp truyền thống, tuy nhiên có một điểm đáng chú ý là nó không có lệnh break để dừng vòng lặp khi cần thiết. 

Tuy nhiên việc forEach trong Stream không cung cấp cơ chế break là vì chúng ta có thể kết hợp sử dụng filter để lọc ra các phần tử cần thiết trước khi duyệt với forEach. Nhưng chúng ta vẫn còn cách khác để làm điều đó trong Stream mà không cần đến filter, hãy theo dõi phần sau.

Stream.takeWhile() Java 9

Từ Java 9, Stream đã thêm takeWhile() method để ngắt Stream khi gặp một phần tử không thoả điều kiện cho trước. 

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

Output:

cat
dog

Chúng ta có thể thấy takeWhile() đã ngắt Stream ngay lập tức khi gặp “elephant” kể cả sau đó có những phần tử thoả điều kiện “fox“. 

Cách triển khai dưới đây sẽ tương ứng trong Java truyền thống tương ứng với cách sử dụng takeWhile() trong Stream:

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);
}

Tuy nhiên, hiện tại Java 8 đang được sử dụng thông dụng hơn Java 9, vậy có nào khác để làm điều tương tự trong Java 8 không? 

Custom Spliterator Java 8

Trong phần này, chúng ta sẽ tạo 1 Spliterator dựa trên Decorator Pattern cung cấp cung cấp cơ chế ngắt Stream tương tự như takeWhile() method trong Java 9.

Trước tiên, chúng ta cần lấy Spliterator từ Stream đang được thao tác, sau đó tạo một CustomSpliterator và cung cấp Predicate control để ngắt Stream. Cuối cùng, chúng ta sẽ tạo ra một Stream mới với CustomSpliterator thay thế Spliterator của Stream.

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);
}

Mã nguồn đầy đủ của CustomSpliterator class.

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;
    }

    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);
    }


    @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;
    }
}

Hãy nhìn tryAdvance() method, đây là xử lý ngắt Stream khi gặp phần tử không thoả Predicate.

public class Test {
    public static void main(String[] agrs) {
        Stream<String> initialStream =
                Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");

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

        System.out.println(result);
    }

}

Output: [cat, dog]

Custom forEach

Ở trên, chúng ta đã cung cấp cơ chế ngắt Stream hoạt động tương tự như takeWhile() Java 9, phần này chúng ta sẽ custom vòng lặp forEach() hoạt động với Stream, với một Beaker tương tự như break trong Java.

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);
            });
        }
    }
}

Như chúng ta có thể thấy vòng lặp forEach được custom sẽ cho phép chúng ta triển khai BiConsumer interface với 2 tham số là phần tử tiếp theo của Stream và breaker object cho phép ngắt Stream khi cần thiết.

public class Test {
    public static void main(String[] agrs) {
        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);
            }
        });
        System.out.println(result);

    }
}

Output:[cat, dog]

Tóm lược

Trong bài viết này, chúng ta đã tìm ra cách để ngắt Stream bằng cách custom Spliterator của Stream. Việc này sẽ rất hữu dụng khi hiện tại Java 8 vẫn đang được sử dụng rộng rãi. Nếu các bạn đang sử dụng Java 9 trở đi thì vấn đề này sẽ được giải quyết nhanh gọn với takeWhile() được thêm vào Stream API.

Nguồn tham khảo

https://www.baeldung.com/java-break-stream-foreach

0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x