Предположим, у меня есть поток вещей, и я хочу «обогатить» их серединой потока, я могу использовать peek()
это, например:
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);
Предположим, что изменение объектов в этой точке в коде является правильным поведением - например, thingMutator
метод может установить в поле «lastProcessed» текущее время.
Однако peek()
в большинстве случаев означает «смотри, но не трогай».
Пользуемся peek()
для мутировать элементы потока в антипаттернах или опрометчиво?
Редактировать:
Альтернативный, более традиционный подход заключается в том, чтобы преобразовать потребителя:
private void thingMutator(Thing thing) {
thing.setLastProcessed(System.currentTimeMillis());
}
к функции, которая возвращает параметр:
private Thing thingMutator(Thing thing) {
thing.setLastProcessed(currentTimeMillis());
return thing;
}
и используйте map()
вместо этого:
stream.map(this::thingMutator)...
Но это вводит небрежный код ( return
), и я не уверен, что он понятнее, потому что, как вы знаете, peek()
возвращает тот же объект, но с первого map()
взгляда даже не ясно, что это тот же класс объекта.
Кроме того, у peek()
вас может быть лямбда, которая мутирует, но с map()
вами придется строить крушение поезда. Для сравнения:
stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)
Я думаю, что peek()
версия более понятна, и лямбда явно мутирует, поэтому нет «таинственного» побочного эффекта. Точно так же, если используется ссылка на метод и название метода явно подразумевает мутацию, это тоже ясно и очевидно.
Что peek()
касается меня , я не уклоняюсь от использования для изменения - я нахожу это очень удобным.
источник
peek
с потоком, который генерирует его элементы динамически? Это все еще работает, или изменения потеряны? Модификация элементов потока звучит для меня ненадежно.List<Thing> list; things.stream().peek(list::add).forEach(...);
очень удобно. Недавно. Я использовал его , чтобы добавить информацию для публикации:Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());
. Я знаю, что есть другие способы сделать этот пример, но я сильно упрощаю здесь. Использованиеpeek()
дает более компактный и более элегантный код IMHO. Помимо читабельности, этот вопрос действительно о том, что вы подняли; это безопасно / надежно?peek
? У меня похожий вопрос по stackoverflow и надеюсь, что вы могли бы посмотреть на него и дать свой отзыв. Спасибо. stackoverflow.com/questions/47356992/…Ответы:
Вы правы, «заглянуть» в английском смысле слова означает «смотри, но не трогай».
Однако в JavaDoc состояния:
Ключевые слова: «совершать ... действие» и «потреблять». JavaDoc очень ясно, что мы должны
peek
иметь возможность изменять поток.Однако JavaDoc также заявляет:
Это указывает на то, что оно предназначено для наблюдения, например, для регистрации элементов в потоке.
То , что я беру из всего этого является то , что мы можем выполнять действия с использованием элементов в потоке, но следует избегать мутирует элементов в потоке. Например, продолжайте и вызывайте методы для объектов, но старайтесь избегать операций с ними над ними.
По крайней мере, я бы добавил краткий комментарий к вашему коду:
Мнения о полезности таких комментариев расходятся, но я бы использовал такой комментарий в этом случае.
источник
thingMutator
, или более конкретнуюresetLastProcessed
и т. Д. Если нет веских причин, комментарии о необходимости, такие как ваше предложение, обычно указывают на неправильный выбор имен переменных и / или методов. Если выбраны хорошие имена, разве этого недостаточно? Или вы говорите, что, несмотря на доброе имя, что-либо в немpeek()
напоминает слепую зону, которую большинство программистов «скользит» (визуально пропускает)? Кроме того, «главным образом для отладки» - это не то же самое, что «только для отладки» - какие предназначения, кроме «в основном», были предназначены?Это может быть легко неверно истолковано, поэтому я бы избегал использовать его таким образом. Вероятно, лучший вариант - использовать лямбда-функцию для объединения двух необходимых действий в вызов forEach. Вы также можете рассмотреть возможность возврата нового объекта, а не изменения существующего объекта - он может быть немного менее эффективным, но, вероятно, приведет к более читаемому коду и уменьшит вероятность случайного использования измененного списка для другой операции, которая должна получили оригинал.
источник
Замечание по API говорит нам, что метод был добавлен в основном для таких действий, как отладка / регистрация / статистика запуска и т. Д.
@apiNote Этот метод существует главным образом для поддержки отладки, где вы хотите * видеть элементы по мере их прохождения через определенную точку в конвейере: *
источник