В потоках Java peek действительно только для отладки?



Я читаю о потоках Java и открываю для себя новые вещи, как я иду вперед. Одна из новых вещей, которые я нашел, была

838   5  

5 ответов:

ключ вынос из этого:

не используйте API непреднамеренно, даже если он выполняет вашу непосредственную цель. этот подход может сломаться в будущем, и это также неясно для будущих сопровождающих.


нет никакого вреда в том, чтобы разбить это на несколько операций, поскольку они являются отдельными операциями. Там и вред при использовании API неясным и непреднамеренным способом, который может иметь последствия, если это особенно поведение изменяется в будущих версиях Java.

используя forEach на этой операции было бы ясно для сопровождающего, что есть предназначены побочный эффект на каждом элементе accounts, и что вы выполняете какую-то операцию, которая может его мутировать.

это также более условно в том смысле, что peek является промежуточной операцией, которая не работает на всей коллекции, пока не будет запущена операция терминала, но forEach действительно a терминальные операции. Таким образом, вы можете сделать сильные аргументы вокруг поведения и потока вашего кода, а не задавать вопросы о if peek будет вести себя так же, как forEach в этом контексте.

accounts.forEach(a -> a.login());
List<Account> loggedInAccounts = accounts.stream()
                                         .filter(Account::loggedIn)
                                         .collect(Collectors.toList());

главное, что вы должны понять, это то, что потоки управляются терминала. Операция терминала определяет, должны ли обрабатываться все элементы или какие-либо вообще. Так что collect - Это операция, которая обрабатывает каждый элемент, в то время как findAny может остановить обработку элементов, как только он столкнулся с соответствующим элементом.

и count() не может обработать какие-то элементы, когда он может определить размер потока без обработки деталей. Поскольку это оптимизация, не выполненная в Java 8, но которая будет в Java 9, могут быть сюрпризы, когда вы переключаетесь на Java 9 и имеете код, полагающийся на count() обработка всех элементов. Это также связано с другими деталями, зависящими от реализации, например, даже в Java 9 эталонная реализация не сможет предсказать размер источника бесконечного потока в сочетании с limit пока нет принципиальных ограничений, препятствующих такому прогнозированию.

С тех пор peek позволяет " выполнять предусмотренное действие на каждом элементе как элементы потребляются из результирующего потока", он не требует обработки элементов, но будет выполнять действие, зависящее от того, что требуется для работы терминала. Это означает, что вы должны использовать его с большой осторожностью, если вам нужна конкретная обработка, например, хотите применить действие ко всем элементам. Он работает, если терминальная операция гарантированно обрабатывает все элементы, но даже тогда вы необходимо быть уверенным, что не следующий разработчик изменит работу терминала (или вы забудете этот тонкий аспект).

кроме того, хотя потоки гарантируют сохранение порядка встреч для определенной комбинации операций даже для параллельных потоков, эти гарантии не применяются к peek. При сборе в список результирующий список будет иметь правильный порядок для упорядоченных параллельных потоков, но peek действие может вызываться в произвольном порядке и одновременно.

так что самое полезное, что вы можете сделать с peek это выяснить, был ли обработан элемент потока, который именно то, что документация API говорит:

этот метод существует в основном для поддержки отладки, где вы хотите видеть элементы, как они проходят мимо определенной точки в конвейере

return list.stream().map(foo->foo.getBar())
                    .peek(bar->bar.publish("HELLO"))
                    .collect(Collectors.toList());

Кажется, это правильный случай, когда вы хотите, чтобы за одну операцию преобразовать все Foos в бары и сказать им всем привет.

Кажется более эффективным и элегантным, чем что-то вроде:

List<Bar> bars = list.stream().map(foo->foo.getBar()).collect(Collectors.toList());
bars.forEach(bar->bar.publish("HELLO"));
return bars;

и вы не закончите итерацию коллекции дважды.

хотя я согласен с большинством ответов выше, у меня есть один случай, в котором использование peek на самом деле кажется самым чистым способом.

аналогично вашему варианту использования, предположим, что вы хотите фильтровать только активные учетные записи, а затем выполнить вход в эти учетные записи.

accounts.stream()
    .filter(Account::isActive)
    .peek(login)
    .collect(Collectors.toList());

Peek полезно, чтобы избежать избыточного вызова, не повторяя коллекцию дважды:

accounts.stream()
    .filter(Account::isActive)
    .map(account -> {
        account.login();
        return account;
    })
    .collect(Collectors.toList());

Я бы сказал, что peek обеспечивает возможность децентрализовать код, который может изменять объекты потока или изменять глобальное состояние (на их основе), вместо того, чтобы набивать все в простая или составная функция передано терминальному методу.

теперь вопрос может быть: должны ли мы мутировать объекты потока или изменять глобальное состояние изнутри функций в функциональном стиле программирования java?

если ответ на любой из вышеперечисленных 2 вопросов да (или: в некоторых случаях да), то peek() и наверняка не только для целей отладки,по той же причине, что forEach() не только для целей отладки.

для меня при выборе между forEach() и peek(), выбирает следующее: Хочу ли я, чтобы куски кода, которые мутируют объекты потока, были прикреплены к составному, или я хочу, чтобы они прикреплялись непосредственно к потоку?

Я думаю peek() будет лучше пара с методами java9. например,takeWhile() возможно, потребуется решить, когда остановить итерацию на основе уже измененного объекта, поэтому очистите его с помощью forEach() не будет иметь такого же эффекта.

П. С. я не ссылался map() везде, потому что в случае, если мы хотим мутировать объекты (или глобальное состояние), а не создавать новые объекты, он работает точно так же, как peek().

Comments

    Ничего не найдено.