Получить последний элемент Stream/List в однострочном
Как я могу получить последний элемент потока или списка в следующем коде?
Где data.careas
является List<CArea>
:
CArea first = data.careas.stream()
.filter(c -> c.bbox.orientationHorizontal).findFirst().get();
CArea last = data.careas.stream()
.filter(c -> c.bbox.orientationHorizontal)
.collect(Collectors.toList()).; //how to?
Как видите, получить первый элемент с определенным filter
несложно.
Однако получить последний элемент в одной строке - настоящая боль:
- Кажется, я не могу получить его прямо из
Stream
. (Это будет иметь смысл только для конечных потоков) - Также кажется, что вы не можете получить такие вещи, как
first()
и last()
из интерфейса List
, что действительно является проблемой.
Я не вижу никаких аргументов для того, чтобы не предоставлять метод first()
и last()
в интерфейсе List
, так как элементы там упорядочены, и, кроме того, размер известен.
Но согласно первоначальному ответу: как получить последний элемент конечного Stream
?
Лично это самое близкое, что я мог получить:
int lastIndex = data.careas.stream()
.filter(c -> c.bbox.orientationHorizontal)
.mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);
Однако это подразумевает использование indexOf
для каждого элемента, что, скорее всего, нежелательно, поскольку это может ухудшить производительность.
Ответы
Ответ 1
Получить последний элемент можно с помощью метода Stream :: проводить. Следующий листинг содержит минимальный пример для общего случая:
Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);
Эта реализация работает для всех упорядоченных потоков (включая потоки, созданные из списков). Для неупорядоченных потоков по очевидным причинам не указано, какой элемент будет возвращен.
Реализация работает как для последовательных, так и для параллельных потоков. На первый взгляд это может удивить, и, к сожалению, в документации это не указано явно. Тем не менее, это важная особенность потоков, и я попытаюсь прояснить это:
- В Javadoc для метода Stream :: lower говорится, что он " не обязан выполняться последовательно ".
- Javadoc также требует, чтобы "функция аккумулятора должна быть ассоциативный, без мешающего, без сохранения функции для объединения двух значений", которые, очевидно, имеет место для лямбда - выражения
(first, second) → second
. - В Javadoc для операций редукции говорится: "Классы потоков имеют несколько форм общих операций редукции, называемых redu() и collect() [..]", и "должным образом сконструированная операция редукции по своей природе распараллеливается до тех пор, пока функция (и) ) используемые для обработки элементов являются ассоциативными и не сохраняют состояния ".
Документация для тесно связанных коллекторов еще более ясна: "Чтобы гарантировать, что последовательные и параллельные выполнения приводят к эквивалентным результатам, функции коллектора должны удовлетворять ограничениям на идентичность и ассоциативность ".
Вернемся к исходному вопросу: следующий код хранит ссылку на последний элемент в переменной last
и выдает исключение, если поток пуст. Сложность линейна по длине потока.
CArea last = data.careas
.stream()
.filter(c -> c.bbox.orientationHorizontal)
.reduce((first, second) -> second).get();
Ответ 2
Если у вас есть коллекция (или более общая итерация), вы можете использовать Google Guava
Iterables.getLast(myIterable)
как удобный oneliner.
Ответ 3
Один вкладыш (нет необходимости в потоке;):
Object lastElement = list.get(list.size()-1);
Ответ 4
Как неясно, является ли это частью спецификации API для reduce
подчиняться порядку встречи, как насчет:
CArea last = data.careas.stream()
.filter(c -> c.bbox.orientationHorizontal)
.max((e1, e2) -> data.careas.indexOf(e1) - data.careas.indexOf(e2)).get();
Ответ 5
Гуава выделил метод для этого случая:
Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);
Это эквивалентно stream.reduce((a, b) → b)
но создатели утверждают, что он имеет гораздо лучшую производительность.
Из документации:
Время выполнения этого метода будет между O (log n) и O (n), что будет лучше работать с эффективно разделяемыми потоками.
Стоит отметить, что если поток неупорядочен, этот метод ведет себя как findAny()
.
Ответ 6
Вы также можете использовать функцию skip(), как показано ниже...
long count = data.careas.count();
CArea last = data.careas.stream().skip(count - 1).findFirst().get();
это супер просто в использовании.
Ответ 7
Как уже упоминалось @nosid, это можно сделать с помощью сокращения.
Другим способом сделать это будет обратный поток, а затем получить первый элемент
CArea last = data.careas.stream()
.filter(c -> c.bbox.orientationHorizontal)
.sorted((a, b)-> -1)
.findFirst().get();
Ответ 8
Вы можете использовать java.util.Collections
, используя следующий статический метод:
Collections.max(yourList);
Из документов:
Возвращает максимальный элемент данной коллекции, в соответствии с * естественное упорядочение его элементов. [...]