1

I have a Map with 1000 items and I want to generate a List from the first 500 items of this Map and remove the items that was collected from the Map.

In other words, I want to filter, collect to a List and remove items from a Map.

I'm trying something like that:

final int i = 0; int max = 5; Map<String, Object> map = new HashMap<>(); map.put("ads", "123"); map.put("qwe", "123"); map.put("cvb", "123"); map.put("asd", "123"); map.put("iop", "123"); map.put("jkl", "123"); map.put("yui", "123"); List list = map.entrySet().stream().filter(y -> i++ < max).collect(Collectors.toList()); 

Expected output:

Map with 2 Values

List with 5 Values

8
  • 1
    It's not clear what you want to do, and the posted code doesn't compile to begin with. Please provide a sample input and expected output of what you want to do, for example with just 10 elements. Commented May 8, 2021 at 22:21
  • Note that you cannot use the Stream any more after executing a terminal operation like .collect or .forEach. Commented May 8, 2021 at 22:24
  • @ÓscarLópez thanks for you feedback, i made some changes to the question and add an input and output. Commented May 8, 2021 at 22:37
  • 6
    Also, do notice that an entrySet() makes no guarantees about the order of its elements. They are not necessarily the first five you added, unless you're using a LinkedHashMap. But in general, the idea of taking "the first n elements" from a map doesn't make sense, if the order is important perhaps you should use a different data structure. Commented May 8, 2021 at 22:42
  • And final means i can't be changed once assigned. So you can't do this filter(y -> i++ < max). And even if you didn't declare i final, local variables must be effectively final in lambdas. Commented May 9, 2021 at 1:16

3 Answers 3

7

Stream API intended to create a new collection, but not to change existing collection, from which a stream created.

It's better use iterator feature in this situation. Like this:

final int MAX = 500; Map<String, String> map = new HashMap<>(); map.put("Item1", "Item 1 value"); map.put("Item2", "Item 2 value"); // ... map.put("Item1000", "Item 1000 value"); List<String> list = new ArrayList<>(); var iterator = map.entrySet().iterator(); for (int i = 0; i< MAX; i++) { if (iterator.hasNext()) { var item = iterator.next(); list.add(item.getValue()); iterator.remove(); } } 
Sign up to request clarification or add additional context in comments.

5 Comments

Works fine with LinkedHashMap for getting the first MAX items. See comment about this question of Óscar López.
Using iterator garantee removing exactly same entries that added to list. No matters LinkedHashMap or HashMap.
yes you are right about the Iterator, and LinkedHashMap garantee removing the first 500 inserted items from the map.
There is no need to iterate over the entrySet() to call getValue() on each entry. You can simply use values() in the first place, var iterator = map.values().iterator(); for(int i = 0; i < MAX && iterator.hasNext(); i++) { list.add(iterator.next()); iterator.remove(); }
Underlying mechanisms for the both iterators backed by the same map. So there is no performance benefit of values().iterator() over entrySet().iterator. But entrySet().iterator() also allows you get access to the entry key.
0

As pointed out in other answer and comments you cannot alter the map during the streaming operations. So this requires two steps.

var list = map.keySet().stream().limit(500).toList(); map.keySet().removeAll(list); 

I've used Java 16 API here but previous versions are minor variations.

Note, however, that is is a pretty meaningless thing to do given the map's order is not defined. It would be more meaningful if you were using a filter with some predicate. Though in that case a more readable alternative might be:

var list = map.keySet().stream().filter(predicate).toList(); map.keySet().removeIf(predicate); 

1 Comment

Or var list = new ArrayList<String>(); map.keySet().removeIf(predicate.and(list::add));
0

The easiest way to achieve this with A SINGLE STREAM is to use Stream#limit, Collectors#collectingAndThen and perform the removeAll inside of the second downstream Collector

This is possible because the first Collectors.toList will get rid of the map's iteration, the second operation can then delete without getting an exception

List<Map.Entry<String, Object>> list = map.entrySet() .stream() .limit(max) .collect(Collectors.collectingAndThen( Collectors.toList(), l -> { map.entrySet().removeAll(l); return l; } )); System.out.println("map = " + map); // map = {qwe=123, cvb=123} System.out.println("list = " + list); // list = [ads=123, asd=123, jkl=123, iop=123, yui=123] 

4 Comments

one of the reasons why I marked it duplicate was .entrySet().stream().limit(max) doesn't guarantee the "first max elements". would like to point you to Oscar's comment.
Instead of removeIf(l::contains) you can use the good old removeAll(l)
Is there any advantage in doing the removeAll as the collector finisher rather than as a separate statement afterwards? Seems unnecessarily verbose to use a lambda block here (and has the risks associated with lambda operations having side effects).
Having a one liner is the idea here. What kind of side effects would you imagine sprinter ?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.