Получить последний элемент 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);

Из документов:

Возвращает максимальный элемент данной коллекции, в соответствии с      * естественное упорядочение его элементов. [...]