1. Обзор
Как разработчики Java, мы часто пишем код, который повторяет набор элементов и выполняет операцию над каждым из них. Библиотека потоков Java 8 и ее для каждого метода позволяют нам писать этот код чистым, декларативным способом.
Хотя это похоже на циклы, нам не хватает эквивалента оператора break для прерывания итерации . Поток может быть очень длинным или потенциально бесконечным , и если у нас нет причин продолжать его обработку , мы хотели бы прервать его, а не ждать его последнего элемента.
В этом уроке мы рассмотрим некоторые механизмы, которые позволяют нам имитировать прерывание оператора в потоке.для каждой операции.
2. Поток Java 9.takeWhile()
Предположим, у нас есть поток String элементов, и мы хотим обрабатывать его элементы до тех пор, пока их длина нечетна.
Давайте попробуем Java 9 Stream.takeWhile метод:
Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck") .takeWhile(n -> n.length() % 2 != 0) .forEach(System.out::println);
Если мы запустим это, мы получим результат:
cat dog
Давайте сравним это с эквивалентным кодом на обычной Java, использующим для цикла и прерывания оператора, чтобы помочь нам понять, как это работает:
Listlist = 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 позволяет нам достичь именно того, что нам нужно.
Но что, если мы еще не приняли Java 9? Как мы можем добиться аналогичного с помощью Java 8?
3. Пользовательский Разделитель
Давайте создадим пользовательский разделитель , который будет работать как декоратор для Stream.разделитель . Мы можем заставить этот Разделитель выполнить разрыв для нас.
Сначала мы получим Разделитель из нашего потока, затем украсим его нашим Пользовательским разделителем и предоставим Предикат для управления операцией break . Наконец, мы создадим новый поток из Пользовательского разделителя:
public staticStream takeWhile(Stream stream, Predicate predicate) { CustomSpliterator customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate); return StreamSupport.stream(customSpliterator, false); }
Давайте рассмотрим, как создать Пользовательский разделитель :
public class CustomSpliteratorextends Spliterators.AbstractSpliterator { private Spliterator splitr; private Predicate predicate; private boolean isMatched = true; public CustomSpliterator(Spliterator splitr, Predicate 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 . Здесь мы видим, что пользовательский Разделитель обрабатывает элементы оформленного Разделителя . Обработка выполняется до тех пор, пока наш предикат сопоставлен и в исходном потоке все еще есть элементы. Когда любое из условий становится ложным , наш Разделитель | “прерывается” и потоковая операция заканчивается.
Давайте протестируем наш новый вспомогательный метод:
@Test public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() { StreaminitialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List result = CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0) .collect(Collectors.toList()); assertEquals(asList("cat", "dog"), result); }
Как мы видим, поток прекратился после выполнения условия. В целях тестирования мы собрали результаты в список, но мы также могли бы использовать вызов forEach или любую другую функцию Stream .
4. Пользовательский интерфейс
Хотя предоставление потока со встроенным механизмом break может быть полезным, может быть проще сосредоточиться только на операции forEach .
Давайте использовать Stream.spliterator напрямую без декоратора:
public class CustomForEach { public static class Breaker { private boolean shouldBreak = false; public void stop() { shouldBreak = true; } boolean get() { return shouldBreak; } } public staticvoid forEach(Stream stream, BiConsumer consumer) { Spliterator spliterator = stream.spliterator(); boolean hadNext = true; Breaker breaker = new Breaker(); while (hadNext && !breaker.get()) { hadNext = spliterator.tryAdvance(elem -> { consumer.accept(elem, breaker); }); } } }
Как мы видим, новый пользовательский метод forEach вызывает Двоичный пользователь , предоставляя нашему коду как следующий элемент, так и объект прерывателя, который он может использовать для остановки потока.
Давайте попробуем это в модульном тесте:
@Test public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() { StreaminitialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List result = new ArrayList<>(); CustomForEach.forEach(initialStream, (elem, breaker) -> { if (elem.length() % 2 == 0) { breaker.stop(); } else { result.add(elem); } }); assertEquals(asList("cat", "dog"), result); }
5. Заключение
В этой статье мы рассмотрели способы обеспечения эквивалента вызова break в потоке. Мы видели, как Java 9 takeWhile решает большую часть проблемы для нас и как предоставить версию этого для Java 8.
Наконец, мы рассмотрели служебный метод, который может предоставить нам эквивалент операции break при повторении потока .
Как всегда, пример кода можно найти на GitHub .