问题
I have the following class:
public class Item {
int id;
String name;
// few other fields, contructor, getters and setters
}
I have a list of Items. I want to iterate through the list and find the instance which has a particular id. I'm trying to do it through streams.
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--;
}
}
Is this the best way to iterate over the list and get the element I need? Also, I get an error on the filter line for id which says variables used in lambda expressions must be final or effectively final. Maybe I can define id inside the while loop, that should get rid of the exception. Thanks.
回答1:
If you have lots of ids to search, it's recommended to use a solution which does it in a single pass rather than doing a linear search for each id:
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
});
}
The first statement simply create a map out of the ids list, mapping each search id to an empty Optional
.
The second statement iterates over the items using forEach
and for each item, it checks whether there’s a mapping from its id to an empty Optional
and will replace it with an Optional
encapsulating the item, if there is such a mapping, all in one operation, computeIfPresent
.
The last for
loop iterates backwards over the ids
list, as you wished to process them in that order and perform the action if there’s a non-empty Optional
. Since the map was initialized with all ids found in the list, get
will never return null
, it will return an empty Optional
, if the id was not found in the items
list.
That way, assuming that the Map
’s lookup has O(1)
time complexity, which is the case in typical implementations, the net time complexity changed from O(m×n)
to O(m+n)
…
回答2:
You can try use something like this:
ids.forEach(id ->
list.stream()
.filter(p -> p.getId() == id)
.findFirst()
.ifPresent(p -> {
// do stuff here
});
);
Optional here shows that your filter method can return a empty stream, so if you call findFirst it can find one or zero elements.
回答3:
If you want to stick with streams and iterate backwards, you could do it this way:
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
}));
This code does the same as yours.
It iterates backwards, starting with a seed: ids.size() - 1
. The initial stream of int
s is limited in its size with limit()
, so that there are no negative int
s and the stream has the same size as the list of ids
. Then, a map()
operation converts the index to the actual id
that is at the ith position at the ids
list (this is done by means of invoking ids.get(i)
). Finally, the item is searched in the items
list the same way as in your code.
回答4:
You want to find at most one item for each given id and do something with the found item, right? A bit more performance improvement:
Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set
items.stream()
.filter(e -> idsToLookup.remove(e.getId()))
.forEach(
/* doing something */
);
来源:https://stackoverflow.com/questions/35928747/java-8-stream-to-find-element-in-list