Java 8 Stream to search for an item in a list

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 a list and find an instance with a specific identifier. I am trying to do this through threads.

 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 a list and get the item I need? In addition, I get an error in the filter line for the identifier, which says that the variables used in lambda expressions should be final or actually final. Perhaps I can define an identifier inside a while loop that should get rid of the exception. Thanks.

+8
java lambda java-8 java-stream
source share
4 answers

If you have many identifiers for searching, it is recommended to use a solution that does this in one pass, and not for a linear search for each identifier:

 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 creates a map from the list of identifiers, matching each search identifier with an empty Optional .

The second statement iterates over the elements using forEach and for each element, it checks whether it matches its identifier with the empty Optional and replaces it Optional , encapsulating the element, if there is such a mapping, all in one operation, computeIfPresent .

The last for loop repeats back in the ids list, since you wanted to process them in that order and perform the action if theres a nonempty Optional . Since the map was initialized with all identifiers found in the list, get will never return null , it will return empty Optional if the identifier is not found in the items list.

Thus, assuming that the Map s search has O(1) time complexity, which is the case in typical implementations, the average time complexity changed from O(m×n) to O(m+n) ...

+7
source share

You can try using something like this:

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

It is optionally shown here that your filter method can return an empty stream, so if you call findFirst, it can find one or zero element.

+9
source share

If you want to stick with threads and iterate backwards, you can do it like this:

 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.

Iterates back, starting from the seed: ids.size() - 1 . The original int stream is limited in size by limit() , so there is no negative int , and the stream is the same size as the ids list. Then the map() operation converts the index into the actual id , which is located in the ith position in the ids list (this is done by calling ids.get(i) ). Finally, the item is scanned in the items list in the same way as in your code.

+2
source share

You want to find no more than one item for each identifier and do something with the found item, right? A little more performance improvement:

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

All Articles