Я читаю о потоках Java и открываю для себя новые вещи. Одна из новых вещей, которую я нашел, была peek()
функция. Почти все, что я читал в peek, говорит, что его следует использовать для отладки ваших потоков.
Что делать, если у меня был поток, где у каждой учетной записи есть имя пользователя, поле пароля и методы 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());
Теперь, насколько я могу судить, это именно то, что он должен делать. Это;
- Принимает список аккаунтов
- Пытается войти в каждую учетную запись
- Отфильтровывает любой аккаунт, который не вошел в систему
- Собирает зарегистрированные аккаунты в новый список
В чем недостаток того, чтобы делать что-то подобное? По какой причине я не должен продолжать? Наконец, если не это решение, то что?
Исходная версия этого использовала метод .filter () следующим образом;
.filter(account -> {
account.login();
return account.loggedIn();
})
java
java-8
java-stream
peek
Adam.J
источник
источник
forEach
может быть операция, которую вы хотите, а неpeek
. То, что он в API, не означает, что он не открыт для злоупотреблений (напримерOptional.of
)..peek(Account::login)
и.filter(Account::loggedIn)
; нет причин писать Consumer and Predicate, который просто вызывает другой метод, подобный этому.forEach()
иpeek()
, может работать только через побочные эффекты; они должны использоваться с осторожностью. ». Мое замечание было больше напоминать, чтоpeek
операцию (которая предназначена для целей отладки) не следует заменять, выполняя то же самое внутри другой операции, такой какmap()
илиfilter()
.Ответы:
Ключ к выводу из этого:
Не используйте API непреднамеренно, даже если он выполняет вашу непосредственную цель. Такой подход может сломаться в будущем, и это также неясно будущим сопровождающим.
Нет ничего плохого в том, чтобы разбить это на несколько операций, поскольку они являются отдельными операциями. Там является вред при использовании API нечетко и непреднамеренным образом, что может иметь последствия , если это конкретное поведение модифицируется в будущих версиях Java.
Использование
forEach
этой операции даст понять сопровождающему, что у каждого элемента есть предполагаемый побочный эффектaccounts
, и что вы выполняете некоторую операцию, которая может изменить его.Это также более привычно в том смысле, что
peek
это промежуточная операция, которая не работает со всей коллекцией до тех пор, пока не будет запущена операция терминала, ноforEach
действительно является операцией терминала. Таким образом, вы можете приводить веские аргументы вокруг поведения и потока вашего кода, а не задавать вопросы о том,peek
будет ли вести себя так же, какforEach
в этом контексте.источник
forEach
прямо у источника коллекции:accounts.forEach(a -> a.login());
login()
метод возвращалboolean
значение, указывающее статус успеха ...login()
возвращает aboolean
, вы можете использовать его как предикат, который является самым чистым решением. У него все еще есть побочный эффект, но это нормально, если он не мешает, то естьlogin
процесс одногоAccount
не влияет на процесс входа другогоAccount
.Важно понимать, что потоки управляются работой терминала . Операция терминала определяет, все ли элементы должны быть обработаны или какие-либо вообще. Такова
collect
операция, которая обрабатывает каждый элемент, в то время какfindAny
может остановить обработку элементов, когда обнаружит соответствующий элемент.И
count()
может вообще не обрабатывать какие-либо элементы, когда он может определить размер потока без обработки элементов. Так как это оптимизация, выполненная не в Java 8, а в Java 9, могут возникнуть неожиданности, когда вы переключитесь на Java 9 и получите код, основанный наcount()
обработке всех элементов. Это также связано с другими деталями, зависящими от реализации, например, даже в Java 9 эталонная реализация не сможет предсказать размер источника бесконечного потока в сочетании сlimit
отсутствием фундаментального ограничения, препятствующего такому прогнозированию.Поскольку
peek
позволяет «выполнить предоставленное действие для каждого элемента, поскольку элементы потребляются из результирующего потока », он не требует обработки элементов, но будет выполнять действие в зависимости от того, что требуется для работы терминала. Это означает, что вы должны использовать его с большой осторожностью, если вам нужна определенная обработка, например, вы хотите применить действие ко всем элементам. Это работает, если работа терминала гарантирует обработку всех элементов, но даже в этом случае вы должны быть уверены, что не следующий разработчик изменит работу терминала (или вы забудете этот тонкий аспект).Кроме того, хотя потоки гарантируют поддержание порядка встречи для определенной комбинации операций даже для параллельных потоков, эти гарантии не распространяются на
peek
. При сборе в список результирующий список будет иметь правильный порядок для упорядоченных параллельных потоков, ноpeek
действие может быть вызвано в произвольном порядке и одновременно.Поэтому самое полезное, что вы можете сделать,
peek
это выяснить, обработан ли элемент потока, что в точности соответствует документации API:источник
Возможно, эмпирическое правило должно заключаться в том, что если вы используете peek вне сценария «отладки», вы должны делать это только в том случае, если вы уверены в том, каковы условия завершения и промежуточной фильтрации. Например:
кажется правильным случаем, когда вы хотите за одну операцию преобразовать все Foos в Bars и передать им всем привет.
Кажется более эффективным и элегантным, чем что-то вроде:
и вам не придется повторять коллекцию дважды.
источник
Я бы сказал, что это
peek
дает возможность децентрализовать код, который может изменять объекты потока или изменять глобальное состояние (на их основе), вместо того, чтобы вставлять все в простую или составную функцию, передаваемую терминальному методу.Теперь возникает вопрос: должны ли мы изменять объекты потока или изменять глобальное состояние изнутри функций в Java-программировании в функциональном стиле ?
Если ответ на любой из вышеуказанных 2 вопросов - да (или: в некоторых случаях да), то
peek()
это определенно не только для целей отладки , по той же причине, чтоforEach()
не только для целей отладки .Для меня при выборе между
forEach()
и яpeek()
выбираю следующее: хочу ли я, чтобы фрагменты кода, которые изменяют объекты потока, были присоединены к компонуемому, или я хочу, чтобы они прикреплялись непосредственно к потоку?Я думаю,
peek()
что лучше будет сочетать с java9 методами. Например,takeWhile()
может потребоваться решить, когда прекратить итерацию на основе уже мутированного объекта, поэтому его сопоставление неforEach()
будет иметь такого же эффекта.PS Я
map()
нигде не ссылался, потому что в случае, если мы хотим изменить объекты (или глобальное состояние), а не генерировать новые объекты, это работает точно так жеpeek()
.источник
Хотя я согласен с большинством ответов, приведенных выше, у меня есть один случай, когда использование peek действительно кажется самым чистым способом.
Как и в вашем случае использования, предположим, что вы хотите фильтровать только по активным учетным записям, а затем выполнить вход в эти учетные записи.
Peek полезен, чтобы избежать избыточного вызова, при этом не нужно повторять коллекцию дважды:
источник
Функциональное решение - сделать объект аккаунта неизменным. Таким образом, account.login () должен возвращать новый объект учетной записи. Это будет означать, что операция карты может быть использована для входа в систему вместо просмотра.
источник