it-swarm.com.ru

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--;
    }
}

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

8
Gengis Khan

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

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, и для каждого элемента он проверяет, есть ли сопоставление его идентификатора с пустым Optional, и заменяет его на Optional, инкапсулирующий элемент, если такое отображение есть, все в одной операции, computeIfPresent.

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

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

8
Holger

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

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

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

9
ByeBye

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

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 (это выполняется с помощью вызова ids.get(i)). Наконец, элемент ищется в списке items так же, как в вашем коде.

2
Federico Peralta Schaffner

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

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set

items.stream()
    .filter(e -> idsToLookup.remove(e.getId()))
    .forEach(
       /* doing something */
     );
0
user_3380739