Java 8 Stream Collectors for Maps, misleading groupingBy error - java

I'm pretty confused by the differences in these two methods, but I'm sure I'm doing something wrong.
I have a working example, and a non-working example below. In the working example, I'm assigning the variable tester as a "Map", In the non-working example I'm trying to assign it to Map. In the second example, the error is shown here:
I am failing to see the connection between the types for the tester variable, and the type of the myMap variable.
//Working Example (or at least it compiles)
public String execute(Map<String, String> hostProps) {
Map tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.groupingBy(myMap -> myMap.get("meterName"))); // This case compiles
}
//Compiler Error, just by adding the Map types on line 13, it breaks line 18
public String execute(Map<String, String> hostProps) {
Map<String, Object> tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.groupingBy(myMap -> myMap.get("meterName"))); // In this case, the .get() method call can't resolve .get. It doesn't recognize that myMap is a Map.
}
//This is the method that gets called in the above methods
static Map<String, Object> prepareUsageForOutput(Usage usage, RateCard rateCard ){
}
Update 1 - Wrong Collector Method
While Eran posted the explanation of my original issue, it revealed that I should have been using Collectors.toMap instead of Collectors.groupBy, because my goal was to return a map entry for each map returned from "prepare", where the key was a string, and the value was the map returned from "prepare". groupBy will always return a list of results as the map value.
I also removed the filter statements, as they were irrelevant and significantly complicated the example.
This was the final result that met my goal:
Map<String, Object> tester = usages.stream()
.map(usage -> prepareUsageForOutput(usage, rateCard.get()))
.collect(Collectors.toMap(myMap ->(String)myMap.get("meterName"), Function.identity()));

Since prepareUsageForOutput returns a Map<String, Object>, this means the Collectors.groupingBy() collector operates on a Stream<Map<String, Object>>, which means it creates a Map<Object,List<Map<String, Object>>>, not a Map<String, Object>.
This answer doesn't explain the error your compiler gave, but several times I encountered misleading compilation errors where Streams are involved, so I suggest changing the output type to Map<Object,List<Map<String, Object>>> and see if the error goes away.

Related

How to call entrySet method on Mono<Map<Entity, Integer>>

Let's say that I have a method findAll which is returning Flux<Song> from ReactiveMongoRepository<Song, String>.
Below I have traditional java stream approach to find the x top-voted songs which is suggested by #Naman:
return songRepository.findAll()
.collect(Collectors.toMap(Function.identity(), this::getSumOfVotes))
.entrySet().stream() --entrySet method is not recognized by IntelliJ
.sorted(Map.Entry.<Song,Integer>comparingByValue().reversed())
.limit(numberOfTopSongs)
.collect(Collectors.toMap(e -> e.getKey().getId(), Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
Above solution works for Iterable, Set and List but the problem occurs when I pretend to call method entrySet on Mono<Map<Song, Integer>>. That's my first dive deep into Web Flux and that's why I am a little bit confused why method entrySet is not available for Mono type.
I have found a piece of code like:
Mono<Map<ByteBuffer, ByteBuffer>> serializedMap = Flux.fromIterable(() -> map.entrySet().iterator())
.collectMap(entry -> rawKey(entry.getKey()), entry -> rawValue(entry.getValue()));
but I try to avoid repacking my:
songRepository.findAll()
.collect(Collectors.toMap(Function.identity(), this::getSumOfVotes))
to Flux.fromIterable again.
I will be grateful for suggestions on what I am doing wrong or tips on how to skip this obstacle.
It's Map<Song, Integer> which has entrySet method; if you want to apply some transformation to contents of a Mono, use map:
Mono<Map<Song, Integer>> songs = ...;
Mono<Set<Map.Entry<Song, Integer>> = songs.map(Map::entrySet);
Note that map is very common name for such a method, it's available on Flux, Stream, etc.

AssertJ: FlatMap list of lists after calling extracting

So I have a Map of String/String list pairs, and what I want to do is after extraction, combine the returned lists into one list on which i can perform more assertions:
MyTest.java
Map<String, List<String>> testMap = new HashMap<>();
List<String> nameList = newArrayList("Dave", "Jeff");
List<String> jobList = newArrayList("Plumber", "Builder");
List<String> cityList = newArrayList("Dover", "Boston");
testMap.put("name", nameList);
testMap.put("job", jobList);
testMap.put("city", cityList);
assertThat(testMap).hasSize(3)
.extracting("name", "city")
//not sure where to go from here to flatten list of lists
// I want to do something like .flatMap().contains(expectedValuesList)
When I call extracting, it pulls out the list values into a list of lists, which is fine, but I cant call flatExtracting after that as there are no property names to pass in, and from what I've read it doesn't seem like a custom extractor would be appropriate(or else I'm not entirely sure how to put it together). Is there another way to flatten the list of lists im getting back? I could go a slightly longer route and do assertions on the list of lists, or use a lambda before the assert to collect the results but I'd like to keep the assertion as one(e.g. some map asserts then chain some assertions on the contents)
flatExtracting is not in the map assertions API (yet), what you can instead is:
assertThat(testMap)
.hasSize(3)
.extracting("name", "city", "job")
.flatExtracting(list -> ((List<String>) list))
.contains("Dave", "Jeff", "Plumber", "Builder", "Dover", "Boston");
I ended creating https://github.com/joel-costigliola/assertj-core/issues/1034 to support this use case

How to explain duplicate entries in Java Hashmap?

This is under multi-threading
I have read topics about HashMap and cannot find relevant questions/answers
The code:
private Map<String, String> eventsForThisCategory;
...
Map<String, String> getEventsForThisCategory() {
if (eventsForThisCategory == null) {
Collection<INotificationEventsObj> notificationEvents = notificationEventsIndex.getCategoryNotificationEvents(getCategoryId());
eventsForThisCategory = new HashMap<String, String>();
for(INotificationEventsObj notificationEvent : notificationEvents) {
eventsForThisCategory.put(notificationEvent.getNotificationEventID(), notificationEvent.getNotificationEventName());
}
logger.debug("eventsForThisCategory is {}", eventsForThisCategory);
}
return eventsForThisCategory;
}
The output:
app_debug.9.log.gz:07 Apr 2016 13:47:06,661 DEBUG [WirePushNotificationSyncHandler::WirePushNotification.Worker-1] - eventsForThisCategory is {FX_WIRE_DATA=Key Economic Data, ALL_FX_WIRE=All, ALL_FX_WIRE=All, FX_WIRE_HEADLINES=Critical Headlines}
How is it possible?
I am quite sure that your map won't have two equal keys at the same time. What you see is an effect of modifying the map while iterating iver it (in toString()). When the second "ALL_FX_WIRE" is written, the first one won't be present in the map any more.
You already know that HashMap is not threadsafe. Plus eventsForThisCategory can get modified by another thread while eventsForThisCategory.toString() is running. So this has to be expected.
Make sure eventsForThisCategory is not modified by multiple threads at the same time (or switch to ConcurrentHashMap), and make sure it is not modified while toString() is running (it is called when you create the debug output).
HashMap is not threadSafety in multi-threading.
you can try to use Collections.synchronizedMap() to wrap you hashMap instance
or use the ConcurrentHashMap maybe better

What is the class of method references?

I have the following code:
List<Person> personList = getPersons();
List<Function<List<Person>, Stream<Person>>> streams = new ArrayList<>();
streams.add(p -> p.stream());
streams.add(p -> p.parallelStream());
Intellij Idea suggests I should replace the lambda expressions to method references.
I'd like to do so, only I'm not sure what should be the new generic type of the streams list.
I tried to evaluate the expression personList::stream but I get "No such instance field: 'stream'". If I try List::stream or ArrayList::stream (The concrete type of the person list) I get: "No such static field: 'stream'".
Is there a way to add method references to a list?
if so what should be the list's generic type?
Thanks
As assylias pointed out, IDEA was just complaining and the code ran without problem,
I still had problems with the same code in IDEA 13 since streams.add expected a function that returns Stream and List::stream returns Stream. To solve it I ended up using the following code:
List<Person> personList = getPersons();
List<Supplier<Stream<Person>>> streams = new ArrayList<>();
streams.add(personList::stream);
streams.add(personList::parallelStream);
This compiles fine (b119):
List<String> personList = Arrays.asList("a", "b");
List<Function<List<String>, Stream<String>>> streams = new ArrayList<>();
streams.add(List::stream);
streams.add(List::parallelStream);
You may be using an old build of the jdk or IntelliJ is messing with you!
personList::stream is basically the same as p -> p.stream(). Neither has a type per se. The type of the expression is the type of the context that accepts it.

ConcurrentModificationException when invoking putAll

I have difficulties in understanding the following error.
Suppose you have a class A in which I implement the following method:
Map<Double,Integer> get_friends(double user){
Map<Double,Integer> friends = user_to_user.row(user);
//friends.putAll(user_to_user.column(user));
return friends;}
Then in the main I do the following:
A obj = new A();
Map<Double,Integer> temp = obj.get_friends(6);
Well this works fine. However when I uncomment the follwing line in class A:
friends.putAll(user_to_user.column(user));
and I run again the program, it crashes and throws me the concurrentModificationException.
It is to be noted, that I am creating the Table user_to_user as follows:
private HashBasedTable<Double,Double,Integer> user_to_user;//
user_to_user = HashBasedTable.create();
What is further surprising is that when I interchange the way I am filling friends, I mean in that way:
Map<Double,Integer> friends = user_to_user.column(user);
friends.putAll(user_to_user.row(user));
Then everyting will work fine.
Any idea ?
The issue is that HashBasedTable is internally implemented as a Map<Double, Map<Double, Integer>>, and that the implementation of user_to_user.column(user) is iterating over the rows at the same time you're modifying the row associated with user.
One workable alternative would be to copy user_to_user.column(user) into a separate Map before putting it into the row.

Categories