Java 8 Stream для поиска элемента в списке

у меня есть следующий класс:

public class Item {
    int id;
    String name;
    // few other fields, contructor, getters and setters
}

у меня есть список предметов. Я хочу перебрать список и найти экземпляр, который имеет определенный идентификатор. Я пытаюсь сделать это через потоки.

public void foobar() {

    List<Item> items = getItemList();
    List<Integer> ids = getIdsToLookup();
    int id, i = ids.size() - 1;

    while (i >= 0) {
        id = ids.get(i);
        Optional<Item> item = items
            .stream()
            .filter(a -> a.getId() == id)
            .findFirst();
        // do stuff
        i--;
    }
}

это лучший способ перебрать список и получить элемент, который мне нужен? Кроме того, я получаю ошибку в строке фильтра для id, которая говорит, что переменные, используемые в лямбда-выражениях, должны быть окончательными или фактически окончательными. Возможно, я могу определить id внутри цикла while, что следует избавиться от исключения. Спасибо.

4 ответов


если у вас есть много идентификаторов для поиска, рекомендуется использовать решение, которое делает это за один проход, а не делать линейный поиск для каждого идентификатора:

Map<Integer,Optional<Item>> map=ids.stream()
    .collect(Collectors.toMap(id -> id, id -> Optional.empty()));
items.forEach(item ->
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item)));
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) {
    map.get(it.previous()).ifPresent(item -> {
        // do stuff
    });
}

первый оператор просто создает карту из списка идентификаторов, сопоставляя каждый идентификатор поиска с пустым Optional.

второй оператор повторяет элементы с помощью forEach и для каждого элемента, он проверяет, есть ли отображение своего ID на пустой Optional и заменит его на Optional инкапсулирование элемента, если такое сопоставление существует, все в одной операции,computeIfPresent.

последние for цикл повторяется назад по ids список, как вы хотели обработать их в этом порядке и выполнить действие, если есть непустой Optional. Поскольку карта была инициализирована всеми идентификаторами, найденными в списке,get никогда не вернется null, он вернет пустой Optional, если идентификатор не найден в items список.

таким образом, предполагая, что С O(1) временная сложность, которая имеет место в типичных реализациях, чистая временная сложность изменилась с O(m×n) to O(m+n)...


вы можете попробовать использовать что-то вроде этого:

ids.forEach(id -> 
    list.stream()
    .filter(p -> p.getId() == id)
    .findFirst()
    .ifPresent(p -> {//do stuff here});
);

необязательно здесь показано, что метод filter может возвращать пустой поток, поэтому при вызове findFirst он может найти один или ноль элементов.


если вы хотите придерживаться потоков и итерации назад, вы можете сделать это следующим образом:

IntStream.iterate(ids.size() - 1, i -> i - 1)
    .limit(ids.size())
    .map(ids::get) // or .map(i -> ids.get(i))
    .forEach(id -> items.stream()
        .filter(item -> item.getId() == id)
        .findFirst().ifPresent(item -> {
            // do stuff
        }));

этот код делает то же самое, как ваша.

он повторяется назад, начиная с семени:ids.size() - 1. Начальный поток ints ограничен в своем размере с limit(), Так что нет отрицательных ints и поток имеет тот же размер, что и список ids. Затем А map() операция преобразует индекс в фактический id то есть на I-й позиции на ids list (это делается с помощью вызова ids.get(i)). Наконец, элемент выполняется поиск в items список так же, как и в вашем коде.


вы хотите найти не более одного элемента для каждого данного идентификатора и сделать что-то с найденным элементом, верно? Немного больше улучшения производительности:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set
items.stream().filter(e -> idsToLookup.remove(e.getId())).forEach(/* doing something */);