Java 8 stream collector breaks HashMap insertion semantics? - java-stream

So I'm trying to use the Java 8 streams Collectors.toMap to add elements to a newly created Map.
The Map to be created may contain null values for some of its keys. This is perfectly acceptable for a HashMap, and when I use a stream forEach to add member it works as expected:
final Map<String, Method> rMap = new HashMap<> ( );
msgRouteProps.entrySet ( )
.stream ( )
.forEach (entry -> rMap.put (entry.getKey ( ),
ReflectionUtil.getNamedMethod (processor, entry.getValue ( ))));
The msgRouteProps is a Map where the keys and values are all non-null. Note that the ReflectionUtil.getNamedMethod () call may return a null, which I want to be put into the resulting Map.
So I wanted to explore how to do the same thing with the Collectors.toMap() method, so the stream operation could return the newly created map, without me having to create it directly.
So I tried the following:
Map<String, Method> rMap =
msgRouteProps.entrySet ( )
.stream ( )
.collect (Collectors.toMap (Map.Entry::getKey,
(e) -> ReflectionUtil.getNamedMethod (processor, e.getValue ( ))));
It seems to me that this should work the same way, but unfortunately, it fails with a null pointer exception. It appears that the Collectors.toMap method calls the HashMap's merge() method which doesn't allow null values to be passed. What seems odd to me is that I don't really think that merge should be getting called at all in this case, since the keys in the original map are all unique. Given that, I would assume that the values could simply be added to the new map via calling the map's put() method, which will allow null values.
Is there something that I'm doing wrong here, or is there a work-around for this? Or is this simply the expected behavior when using Collectors.toMap?

It's a known bug already been reported by many users
The best way is to use forEach if you have null values. Still if you want to use collect then try the following work around
Map<String, Method> rMap =
msgRouteProps.entrySet()
.stream()
.collect(HashMap::new, (m,v) -> m.put(v.getKey(), ReflectionUtil.getNamedMethod(processor, v.getValue())), HashMap::putAll);
Also take a look at this answer as well

Related

Dart: How to check if List of Objects is a List of Strings/ints/bools

For simple data types, you can use e.g.
object is String
to check whether an Object variable is of a more specific type.
But let's you have a List, but want to check if it is a List of Strings. Intuitively we might try
List list = ['string', 'other string'];
print(list is List<String>);
which returns false.
Similarly using the List.cast() method doesn't help, it will always succeed and only throw an error later on when using the list.
We could iterate over the entire list and check the type of each individual entry, but I was hoping there might be a better way.
There is no other way. What you have is a List<Object?>/List<dynamic> (because the type is inferred from the variable type, which is a raw List type which gets instantiated to its bound). The list currently only contains String objects, but nothing prevents you from adding a new Object() to it.
So, the object itself doesn't know that it only contains strings, you have to look at each element to check that.
Or, when you create a list, just declare the variable as List<String> list = ...; or var list = ...;, then the object will be a List<String>.
If you are not the one creating the list, it's back to list.every((e) => e is String).
Each element of a List may be of any type BUT IF YOU ARE SURE that all elements have the same type this approach may be useful
bool listElementIs<T>(List l) {
if (l.isEmpty) return true;
try {
if (l[0] is T) return true; // only try to access to check element
} catch (e) {
return false;
}
return false;
}
void main() {
List list = ['string', 'other string'];
print(listElementIs<String>(list)); // prints 'true'
print(listElementIs<int>(list)); // prints 'false'
}
I think a better way to do this is to be specific about your data types.
By specifying the types of variables, you can catch potential errors early on during the development process.
In addition, specifying the types of variables makes the code more readable and understandable.
Furthermore, specifying types can also improve the performance of your code, as the compiler can make certain optimizations based on the types of variables.
List<String> list = <String>['string', 'other string'];
print(list is List<String>); /// prints true.
You can use this linter rule to enforce it:
https://dart-lang.github.io/linter/lints/always_specify_types.html

'List<dynamic>' is not a subtype of type 'List<Comment>'. Why can't Dart infer this type? How do I fix this?

I have nested comments. In order to fetch them from JSON, I have the following fromJson() function:
Comment.fromJson(Map<String, dynamic> json)
: id = json['id'],
...
children = json['children'] != null
? json['children'].map((c) => Comment.fromJson(c)).toList()
: null,
...
This checks if the current comment has any children comments, and if so, recursively parses them from JSON into a Comment. However when I run this, I get the following error:
[ERROR:flutter/shell/common/shell.cc(214)]
Dart Unhandled Exception:
type 'List<dynamic>' is not a subtype of type 'List<Comment>'
stack trace: #0 new Comment.fromJson (package:app/models/comment.dart:43:18)
Which points to the line where I assign children with children = json.... I'm new to dart, but I don't understand how mapping over a list, and returning a Comment doesn't let Dart infer the type of the list. How do I fix this? Adding as List<Comment> after toList() didn't work. If I add as Comment after Comment.fromJson(c), I get unnecessary cast.
Dart can't infer anything from json['children'].map(...).toList() because json['children'] is of type dynamic, and therefore it doesn't know what the .map call is going to resolve to at runtime.
At runtime, since .map is called without an explicit type, it ends up being a call to .map<dynamic>(...), and then .toList() returns List<dynamic>.
Try either:
Explicitly using .map<Comment>(...).
Casting json['children']: (json['children'] as List<Map<String, dynamic>>).map(...). It'd probably be a good idea to validate this anyway.

Is there a way to map a Map to a List in Dart?

I want to turn a map into a list of objects that contain information from the key and value of each map entry.
I know it is possible to do it this way:
myMap.map<String,Widget>((key, value) {
return MapEntry<String, Widget>(key,Widget(
//Some widget that uses key and value
));
}).values.toList();
However, I am wondering if there is a simpler way to do it without turning it into a map first. Something like:
myMap.map<Widget>((key, value) {
return Widget(
//Some widget that uses key and value
);
});
However this returns an error:
"Error: Expected 2 type arguments."
If I understand the question correct, you can map .entries instead, like this:
myMap.entries.map((e) => Widget( /* use e.key and e.value */ )).toList()

how do I get non-server values mapped back from client?

I am setting properties in client-side Breeze entities. I see the EntityInfo UnmappedValuesMap collection, which I'd like to use. It would be great if I could get a few custom client values returned in the Unmapped collection to avoid adding these everywhere.
I am initializing the metastore with:
store.registerEntityTypeCtor("UserInfo", null, userInfoInitializer);
function userInfoInitializer(userinfo) {
userinfo.creatingId = ko.observable(0);
...
I was hoping 'creatingId' would get passed to server. But nothing extra appears in the net traffic.
I don't think it matters, but on the server I am using Breeze.ContextProvider.
Are there flags somewhere that govern this behavior? Thanks for any guidance.
If you wanted to add a 'creatingId' to every entity type you could do something like this:
metadataStore.getEntityTypes().forEach(
function(entityType) {
var ctor = function () {
this.creatingId = 0;
};
metadataStore.registerEntityTypeCtor(entityType.name, ctor, null);
});
Note- using a constructor instead of an initializer. This will ensure the value appears in the unmapped values collection. Don't worry, breeze will make this an observable property on your entity.

Strange behavior of gorm finder

In a controller I have this finder
User.findByEmail('test#test.com')
And works.
Works even if I write
User.findByEmail(null)
But if i write
User.findByEmail(session.email)
and session.email is not defined (ergo is null) it throw exception
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
Is this behavior right?
If i evaluate "session.email" it give me null so I think it must work as it do when I write
User.findByEmail(null)
Even more strange....
If I run this code in groovy console:
import myapp.User
User.findByEmail(null)
It return a user that has null email but if I run the same code a second time it return
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
You can't use standard findBySomething dynamic finders to search for null values, you need to use the findBySomethingIsNull version instead. Try
def user = (session.email ? User.findByEmail(session.email)
: User.findByEmailIsNull())
Note that even if User.findByEmail(null) worked correctly every time, it would not necessarily give you the correct results on all databases as a findBySomething(null) would translate to
WHERE something = null
in the underlying SQL query, and according to the SQL spec null is not equal to anything else (not even to null). You have to use something is null in SQL to match null values, which is what findBySomethingIsNull() translates to.
You could write a static utility method in the User class to gather this check into one place
public static User byOptEmail(val) {
if(val == null) {
return User.findByEmailIsNull()
}
User.findByEmail(val)
}
and then use User.byOptEmail(session.email) in your controllers.
Jeff Brown from grails nabble forum has identified my problem. It's a GORM bug. see jira
More info on this thread
This jira too
I tried with debugger and it looks it should be working, as you write. Maybe the groovy itself is a little bit confused here, try to help it this way:
User.findByEmail( session['email'] )

Resources