Comments 50
Там не очень много. Почти всё перечислено, например, здесь
У джавы своя философия. В частности, в джаве ценится ясность кода. Наличие методов типа map прямо у коллекций не даёт явного понимания, метод ленивый или нет, будет ли промежуточная коллекция, если сделать два раза map или нет. А со стримами всё понятно. Вы можете сказать, мол, давайте скажем, что у коллекции все методы неленивы. Но если сама коллекция — это вьюшка над другой коллекцией? Скала тут как раз засорила все абстрактные интерфейсы кучей всего. Пришёл вам Traversable
, вы вызываете у него map
. Что вы получите, новую независимую копию или вьюшку над старой? Зависит от того, что вам на самом деле там передали, TraversableView
или тупо List
. По факту протекающая абстракция. Кому-то нравится такой подход, абстрагировать всё и вся, те пишут на Скале. Мне нравится, когда я лучше понимаю, что в моём коде происходит, поэтому я пишу на Джаве. Это замечательно, что есть разные языки с разной философией.
Guava Collections неплохи, и с лямбдами получается достаточно компактный код.
Примеры кажутся нереальными) неужели действительно все они из реальных проектов?)
В примере 2.3 наверное имелось в виду b -> true, а не b->b?
Нет-нет, именно b->b
.
Ваш пример интересен. sort() меняет текущий список. По сути дела замена — это два выражения, List<T> sortedParams = new ArrayList(params);sortedParams.sort(...);
. Это заметно оптимальнее, но можно спорить, красивее ли. Теперь, к примеру, sortedParams
не заинлайнишь, к примеру.
params.stream().sorted(...).collect(Collectors.toList())
А если нужно сохранить исходный params в неприкосновенности? Коллектор toList ведь порождает новую коллекцию, не трогая ту, из которой приготовлен стрим.
Насчёт примера с sorted: если бы копия списка там была нужна! Но копия списка там была не нужна.
Я ждал этого! IDEA-178614
А для Optional
и CompletionStage
такие статьи ожидаются?
Меня удручает, что оба Stream
и Optional
имеют метод #map(map(Function<? super T, ? extends U> mapper)
, который выглядит одинаково, но имеет несколько разный смысл:
- в
Optional
mapper вызовется только для ненулевого элемента - в
Stream
mapper вызовется для любых элементов стрима (нужно делатьfilter()
, если хотим исключить нулевые)
Сейчас уже привык и веду себя осторожнее, но вижу иногда, как новички наступают на те же грабли.
"Нулевой элемент" в Optional обозначает отсутствие элемента. Как в принципе можно вызвать mapper для отсутствующего элемента?
Или с другой стороны: Optional делали чтобы избавиться от null. Как можно использовать Optional и при этом ожидать где-то null?
У Optional
действительно есть особенность, нарушающая монадический закон: map(map(opt, F), G) ≠ map(opt, G○F)
или в Java-терминах opt.map(F).map(G) != opt.map(G.compose(F))
. Если F возвращает null для какого-либо значения, то в композиции G выполнится с аргументом null, а в Optional
G не выполнится вообще. Пуристы в этом месте начинают плеваться. Ну и, например, соответствующий рефакторинг для Optional, который предлагает IDEA, по факту меняет семантику кода (а для Stream не меняет).
Готово
An English translation of my recent article with several additions https://t.co/C9j6YzeGXZ
— Tagir Valeev (@tagir_valeev) September 9, 2017
А еще есть rx, там вообще ад. :D
List<T> temp = collection.stream().filter(...).map(...).collect(Collectors.toList());
... some unrelated to "temp" stuff...
List<T> result = temp.stream().filter(...).map(...).collect(Collectors.toList());
Вместо
List<T> result = collection.stream().filter(...).map(...).filter(...).map(...).collect(Collectors.toList());
Да, я такое тоже встречал. К сожалению, автоматически предлагать склеить это в одну цепочку опасно. Может человек полагается на сайд-эффекты и ему важно, чтобы все первые операции выполнились до вторых. Классический пример:
List<CompletableFuture<T>> futures = callables.stream()
.map(CompletableFuture::supplyAsync).collect(toList());
List<T> results = futures.stream()
.map(CompletableFuture::join).collect(toList());
Здесь если склеить в один стрим, то все фоновые задачи будут выполняться последовательно (причём этого можно не заметить, скорее всего тесты не упадут).
List<String> list = Arrays.asList("A", "B", "C");
List<String> copy = list.stream().map(String::new).collect(Collectors.toList());
Тут на самом деле есть некоторая ментальная ловушка.
Когда мы говорим о методе min или max, то невольно в голове ссылаемся на числовое представление.
А когда объекты в стриме мы не представляем как последовательность чисел, то некоторые и не думают, что можно использование данные операции.
То есть если бы методы назывались last(Comparator) и first(Comparator), то скорее всего эти методы были бы использованы.
То есть разработчик здесь говорит упорядочи каким-то способом набор объектов и возьми самый первый(Он не думает про объект в терминах минимальный/максимальный).
То есть если бы методы назывались last(Comparator) и first(Comparator), то скорее всего эти методы были бы использованы.
До чтения javadoc/просмотра реализации, ориентируясь только по названиям, я бы решил, что это методы, возвращающие первое и последнее по порядку следования вхождения элементов в stream, на которые сделал стойку компаратор. А вот с min
/ max
всё однозначно.
То есть разработчик здесь… не думает про объект в терминах минимальный/максимальный.
Это неправильный разработчик и он даёт неправильный мёд.
peek
final AtomicBoolean streamIsEmpty = new AtomicBoolean(true);
final boolean anyMatch = someStream(...)
.map(...)
.peek(obj -> streamIsEmpty.set(false))
.anyMatch(condition);
return anyMatch || streamIsEmpty.get();
Можно придумать вариант лучше?Лучше, наверное, в таких случаях свой Collector писать.
Собственно да. С помощью стандартного Stream API вроде красиво не решить. Моя библиотека StreamEx позволяет легко сделать такой коллектор:
static <T> Collector<T, ?, Boolean> anyMatchOrEmpty(Predicate<T> predicate) {
return MoreCollectors.pairing(
MoreCollectors.filtering(predicate, MoreCollectors.first()),
MoreCollectors.first(),
(filtered, nonFiltered) -> filtered.isPresent() || !nonFiltered.isPresent());
}
С обычным стримом он не будет короткозамкнут, но если источник обернуть в StreamEx (или если источник сразу возвращает StreamEx), то будет:
return StreamEx.of(someStream(...)).collect(anyMatchOrEmpty(condition));
Boolean anyMatch = someStream()
.map(condition)
.reduce(null, (a, b) -> a == null ? b : a || b);
return anyMatch == null || anyMatch;
Потребует некоторого времени со стороны разработчика, который будет пытаться понять, зачем такой сложный редьюс (можно снабдить комментарием), но выглядит немного изящнее, чем добавление AtomicBoolean.
Насчёт IDE не заменяет голову, кстати, пример. IDEA предупредит, если вы напишете Arrays.asList(array).stream()
. Но если у вас сложный внешний стрим и в нём встретится .map(Arrays::asList).flatMap(List::stream)
вместо простого .flatMap(Arrays::stream)
, то тут уже не предупредит. Или про filter().findFirst().isPresent()
скажет, но если вы промежуточный Optional
присвоите в переменную и используете её только для isPresent
, то уже увы. Может когда-нибудь и эти случаи покроем, но не стоит надеяться, что IDE вам подскажет всегда.
1.1. collection.stream().forEach()
…
Напишите просто collection.forEach().
Если для метода не передается Consumer из вне, то лучше использовать старый-добрый форыч.
for (T it : collection) {
...
}
Более того, обычно метод forEach() используют в связке с замыканием, чтобы туда сложить значения. Что еще более печально выглядит.
ps: Я до сих пор не понимаю, почему разработчики Java решили добавить Stream API непосредственно в интерфейс коллекция?
Ведь практически всегда можно: Stream.of(… ) и погнали. Зачем было уродовать интерфейс коллекций? Притом с подходом Stream.of – коллекции бы не зависели от Стримов, а так получается довольно жесткая связанность между Стримом и Коллекцией :(
forEach
изящнее выглядит со ссылкой на метод. Кроме того может быть несколько быстрее, потому что не создаётся итератор.
Я до сих пор не понимаю, почему разработчики Java решили добавить Stream API непосредственно в интерфейс коллекция?
Ну вот любят люди всё такое fluent-fluent. Все вон трещат, чтобы им прямо в коллекцию добавили map
или filter
. Написать свой утилитный класс и вызывать статические методы религия не позволяет. Stream.from(veryLongCallProducingACollection).many().stream().operations()
тоже смотрится не очень.
Вообще с точки зрения разработчиков Java, стримы более базовая вещь, чем коллекции. От коллекций по факту зависит только класс Collectors
, который утилитный. Остальные стримы от коллекций не зависят.
forEach изящнее выглядит со ссылкой на метод. Кроме того может быть несколько быстрее, потому что не создаётся итератор.Изящнее это только с ссылкой на метод, согласен. Но если пишут лямбду или того хуже – лямбду-многострочник, это смотрится хуже, особенно с захватом переменной.
А по производительности скорее всего будет одинаково (в приделах погрешности).
стримы более базовая вещь, чем коллекции.Стримы – это фреймворк для работы с данными, который свободно может стоять особняком.
Да, конечно в ситуации, когда стрим порождает коллекцию из которой берется новый стрим этот код смотрелся бы отвратно, но ведь если так делается, то это уже признак, что что-то делается не так, как следовало бы.
list.stream().(transforming).collect(toList()).stream(). ...
хотя, я наверно еще не сталкивался с реально сложной работой с данными, где действительно пригодился бы .stream() как интерфейсный метод.
docs.oracle.com/javase/tutorial/collections/streams/parallelism.html#ordering
здесь сказано, что порядок всё же гарантируется. А где Вы нашли инфу, что не гарантируется?
Используйте Stream API проще (или не используйте вообще)