заглянуть в параллельный поток для увеличения счетчика [dубликат]

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

Что делать, если у меня есть Stream, в котором у каждой учетной записи есть имя пользователя, пароль и метод login () и loggedIn () .

У меня также есть

Consumer<Account> login = account -> account.login();

и

Predicate<Account> loggedIn = account -> account.loggedIn();

Почему это было бы так плохо?

List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount = 
accounts.stream()
    .peek(login)
    .filter(loggedIn)
    .collect(Collectors.toList());

Now насколько я могу сказать, это делает именно то, что он намерен делать. Он:

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

. Что такое что делать? По какой-то причине я не должен продолжать? Наконец, если это не решение, то что?

Исходная версия этого метода использовала метод .filter () следующим образом:

.filter(account -> {
        account.login();
        return account.loggedIn();
    })
76
задан 11 November 2015 в 02:41

4 ответа

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

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

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

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

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

Этот метод существует главным образом для поддержки отладки, где вы хотите видеть элементы по мере их прохождения определенная точка в конвейере
68
ответ дан 15 August 2018 в 16:36
  • 1
    будут ли какие-либо проблемы, будущие или настоящие, в случае использования ОП? Всегда ли его код делает то, что он хочет? – ZhongYu 10 November 2015 в 22:06
  • 2
    @ bayou.io: насколько я вижу, в нет проблемы в этой точной форме . Но, как я пытался объяснить, использование этого метода подразумевает, что вы должны помнить этот аспект, даже если вы вернетесь к коду через один или два года, чтобы включить «запрос функции 9876» в код ... – Holger 10 November 2015 в 22:09
  • 3
    @Jose Martinez: В нем говорится: «Когда элементы потребляются из результирующего потока », что не является терминальным действием, а обработкой, хотя даже терминальное действие может потреблять элементы не по порядку, пока конечный результат согласован. Но я также думаю, что фраза примечания API: « видеть элементы, когда они проходят мимо определенной точки в конвейере », лучше справляется с ее описанием. – Holger 3 July 2017 в 13:42

Возможно, эмпирическое правило должно состоять в том, что если вы используете заглянуть за рамки сценария «отладки», вы должны сделать это только в том случае, если вы уверены в том, что условия завершения и промежуточной фильтрации. Например:

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

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

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

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

, и вы не завершаете повторную сборку дважды.

9
ответ дан 15 August 2018 в 16:36

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

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

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

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

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

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

1
ответ дан 15 August 2018 в 16:36

Хотя я согласен с большинством ответов выше, у меня есть один случай, когда использование 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());
2
ответ дан 15 August 2018 в 16:36
  • 1
    Все, что вам нужно сделать, - это правильно использовать этот метод входа. Я действительно не понимаю, как быстрый взгляд - это самый чистый путь. Как кто-то, кто читает ваш код, знает, что вы фактически злоупотребляете API. Хороший и чистый код не заставляет читателя делать предположения о коде. – kaba713 3 July 2018 в 11:01

Другие вопросы по тегам:

Похожие вопросы: