HashMap null check in Merge Operation - java-8

Why HashMap merge is doing null check on value. HashMap supports null key and null values.So can some one please tell why null check on merge is required?
#Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
Due to this I am unable to use Collectors.toMap(Function.identity(), this::get) to collect values in a Map

The behavior is mandated by the Map.merge contract:
Throws:
…
NullPointerException - if the specified key is null and this map does not support null keys or the value or remappingFunction is null
Note that using Map.merge for Collectors.toMap without a merge function is an implementation detail; it not only disallows null values, it does not provide the desired behavior for reporting duplicate keys, the Java 8 implementation wrongly reports one of the two values as key when there are duplicate keys.
In Java 9, the implementation has been completely rewritten, it does not use Map.merge anymore. But the new implementation is behavioral compatible, now having code explicitly throwing when the value is null. So the behavior of Collectors.toMap not accepting null values has been fixed in the code and is not an artifact of using Map.merge anymore. (Still speaking of the toMap collector without a merge function only.)
Unfortunately, the documentation does not tell.

Because internally for Collectors.toMap, Map#merge is used - you can't really do anything about it. Using the static Collectors.toMap is not an option (which by the way is documented to throw a NullPointerException).
But spinning a custom collector to be able to do what you want (which you have not shown) is not that complicated, here is an example:
Map<Integer, Integer> result = Arrays.asList(null, 1, 2, 3)
.stream()
.collect(
HashMap::new,
(map, i) -> {
map.put(i, i);
},
HashMap::putAll);

As a workaround for mentioned problems with null values in toMap and merge
you can try to use a custom collector in the following manner:
public static <T, R> Map<T, R> mergeTwoMaps(final Map<T, R> map1,
final Map<T, R> map2,
final BinaryOperator<R> mergeFunction) {
return Stream.of(map1, map2).flatMap(map -> map.entrySet().stream())
.collect(HashMap::new,
(accumulator, entry) -> {
R value = accumulator.containsKey(entry.getKey())
? mergeFunction.apply(accumulator.get(entry.getKey()), entry.getValue())
: entry.getValue();
accumulator.put(entry.getKey(), value);
},
HashMap::putAll);
}

Related

How is possible the Map will find the right element, when the HasCode() of that element has changed?

From my previous question: Hibernate: Cannot fetch data back to Map<>, I was getting NullPointerException after I tried to fetch data back. I though the reason was the primary key (when added to Map as put(K,V), the primary key was null, but after JPA persist, it created the primary key and thus changed the HashMap()). I had this equals and hashCode:
User.java:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(username, user.username) && Objects.equals(about, user.about) && Objects.equals(friendships, user.friendships) && Objects.equals(posts, user.posts);
}
#Override
public int hashCode() {
return Objects.hash(id, username, about, friendships, posts);
}
-> I used all fields in the calculation of hash. That made the NullPointerException BUT not because of id (primary key), but because of collections involved in the hash (friends and posts). So I changed both functions to use only database equality:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (id == null) return false;
if (!(o instanceof User)) return false;
User user = (User) o;
return this.id.equals(user.getId());
}
#Override
public int hashCode() {
return id == null ? System.identityHashCode(this) :
id.hashCode();
So now only the id field is involved in the hash. Now, it didn't give me NullPointerException for fetched data. I used this code to test it:
(from User.java):
public void addFriend(User friend){
Friendship friendship = new Friendship();
friendship.setOwner(this);
friendship.setFriend(friend);
this.friendships.put(friend, friendship);
}
DemoApplication.java:
#Bean
public CommandLineRunner dataLoader(UserRepository userRepo, FriendshipRepository friendshipRepo){
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
User f1 = new User("friend1");
User f2 = new User("friend2");
User u1 = new User("user1");
System.out.println(f1);
System.out.println(f1.hashCode());
u1.addFriend(f1);
u1.addFriend(f2);
userRepo.save(u1);
User fetchedUser = userRepo.findByUsername("user1");
System.out.println(fetchedUser.getFriendships().get(f1).getFriend());
System.out.println(fetchedUser.getFriendships().get(f1).getFriend().hashCode());
}
};
}
You can see I am
puting the f1 User into friendship of user1 (owner of the friendship). The time when the f1.getId() == null
saving the user1. The time when the f1 id gets assign its primary key value by Hibernate (because the friendship relation is Cascade.All so including the persisting)
Fetching the f1 User back by geting it from the Map, which does the look-up with the hashCode, which is now broken, because the f1.getId() != null.
But even then, I got the right element. The output:
User{id=null, username='friend1', about='null', friendships={}, posts=[]}
-935581894
...
User{id=3, username='friend1', about='null', friendships={}, posts=[]}
3
As you can see: the id is null, then 3 and the hashCode is -935581894, then 3... So how is possible I was able to get the right element?
Not all Map implementation use the hashCode (for example a TreeMap implementation do not use it, and rather uses a Comparator to sort entries into a tree).
So i would first check that hibernate is not replacing the field :
private Map<User, Friendship> friendships = new HashMap<>();
with its own implementation of Map.
Then, even if hibernate keeps the HashMap, and the hashcode of the object changed, you might be lucky and both old and new hashcodes gives the same bucket of the hashmap.
As the object is the same (the hibernate session garantees that), the equals used to find the object in the bucket will work. (if the bucket has more than 8 elements, instead of the bucket being a linked list, it will be a b-tree ordered on hashcode, in that case it won't find your entry, but the map seems to have only 2-3 elements so it can't be the case).
Now I understood your question.
Looking at the Map documentation we read the following:
Note: great care must be exercised if mutable objects are used as map
keys. The behavior of a map is not specified if the value of an object
is changed in a manner that affects equals comparisons while the
object is a key in the map.
It looks like there is no definitive answer for this and as #Thierry already said it seems that you just got lucky. The key takeaway is "do not use mutable objects as Map keys".

How to cast elements in an instance of List with streams in Java 8?

Given the code:
public Statement methodCallByName(MethodDeclaration method, String string) {
List<ExpressionStatement> expressions = method.getBody().statements().stream()
.filter(s -> s instanceof ExpressionStatement)
.map(ExpressionStatement.class::cast)
.collect(Collectors.toList());
return null;
}
I have the following error in Eclipse Oxygen:
Notice that statements() returns a List according to JDT docs.
What is wrong?
The problem is caused by statements() returning the raw type List (see also What is a raw type and why shouldn't we use it?).
Raw types may not only provoke unchecked operations, but also limit the applicability of type inference.
You may fix it with
public Statement methodCallByName(MethodDeclaration method, String string) {
List<?> statements = method.getBody().statements();
List<ExpressionStatement> expressions = statements.stream()
.filter(s -> s instanceof ExpressionStatement)
.map(ExpressionStatement.class::cast)
.collect(Collectors.toList());
// ...
return null;
}
The conversion from the raw type List to a list of unknown element type List<?>, is the only type safe conversion we can do here. Since you are going to check and cast the elements anyway, that’s no restriction.
But note that you should try to be consistent. Use either
.filter(s -> s instanceof ExpressionStatement)
.map(s -> (ExpressionStatement)s)
or
.filter(ExpressionStatement.class::isInstance)
.map(ExpressionStatement.class::cast)

How to use lambda/Stream api to filter distinct elements by object Attribute/Property

I have a List of Object. Every object has a map with a key named "xyz". I want elements in the list which has unique value to that particular key.
I know we can do this easily with set/map but I'm particularly looking for lambda solution.
I thought this would work.
list.stream()
.filter(distinctByXyz(f -> f.getMap.get("xyz")))
.collect(Collectors.toList()));
I've a function to distinct them
private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor){
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
The problem is the function f.getMap() inside filter isnt working. Showing compilation error (Cannot resolve method)
You seem to have a few typos in your code, this should work:
list
.stream()
.filter(distinctByKey(f -> f.getMap().get("xyz")))
.collect(Collectors.toList());
You are using distinctByXyz when it should really be distinctByKey. Then f.getMap that should probably be f.getMap() and also you are slightly off with your parenthesis.

Use hashCode() for sorting Objects in java, not in HashTable and ect

I need your help.
If i want to sort a PriorityQeueu in java, with out connection to it's attributes - could i use the hashCode's Objects to compare?
This how i did it:
comp = new Comparator<Person>() {
#Override
public int compare(Person p1, Person p2) {
if(p1.hashCode() < p2.hashCode()) return 1;
if(p1.hashCode() == p2.hashCode()) return 0;
return -1;
}
};
collector = new PriorityQueue<Person>(comp);
It doesn't sound like a good approach.
Default hashCode() is typically implemented by converting the internal address of the object into an integer. So the order of objects will differ between application executions.
Also, 2 objects with the same set of attribute values will not return the same hashCode value unless you override the implementation. This actually breaks the expected contract of Comparable.

Combo Box Item comparison and compiler warnings

In VisualStudio (Pro 2008), I have just noticed some inconsistent behaviour and wondered if there was any logical reasoning behind it
In a WinForms project, if I use the line
if(myComboBox.Items[i] == myObject)
I get a compiler warning that I might get 'Possible unintended references' as I am comparing type object to type MyObject. Fair enough.
However, if I instead use an interface to compare against:
if(myComboBox.Items[i] == iMyInterface)
the compile warning goes away.
Can anyone think if there is any logical reason why this should happen, or just an artifact of the compiler not to check interfaces for comparison warnings. Any thoughts?
EDIT In my example, the combobox was bound to a List, but that list was generated using list<IMyInterface>.Cast<MyObject>().ToList<MyObject>()
Its as if the compiler is only taking still assuming I am binding to the List of IMyInterface.
(Object and Interface methods have been changed to protect the innocent)
The compile warning for the first sample is because any custom == operator for your class would be ignored and the references compared (maybe not what you intended, hence the warning).
It's not possible to specify that an operator should be overridden on an interface, so this will always be a reference comparison. The warning is not needed because you should always expect this.
Here's an example of overriding the == operator:
class Program
{
static void Main(string[] args)
{
object t1 = new MyTest() { Key = 1 };
MyTest t2 = new MyTest() { Key = 1 };
Console.WriteLine((MyTest)t1 == t2); // Uses overriden == operator, returns true
Console.WriteLine(t1 == t2); // Reference comparison, returns false
}
}
public class MyTest
{
public int Key { get; set; }
public override bool Equals(object obj)
{
return this.Key == (obj as MyTest).Key;
}
public override int GetHashCode()
{
return this.Key.GetHashCode();
}
public static bool operator ==(MyTest t1, MyTest t2)
{
return t1.Equals(t2);
}
public static bool operator !=(MyTest t1, MyTest t2)
{
return !t1.Equals(t2);
}
}
The MyTest class is considered equal if the Key property is equal. If you were to create an interface, you cannot specify that it should include a custom == operator and therefore the comparison would always be a reference comparison (and therefore false in the case of our sample code).
Lagerdalek,
The warning is generated because you need to cast the item from the Items collection back into the orginal type that was bound to the combo box, before comparing; otherwise you may get unexpected results as the compiler warns.
Here is an example:
myComboBox.DataSource = Collection<Car>;
So if the combo box is bound to a collection of car objects you would cast them back before comparison:
if((car)myComboBox.Items[i] == thisCar)
Then you shouldn't get any warnings.
Another method you could do is:
using(myComboBox.Items[i] as car){
if(myComboBox.Items[i] == thisCar)
}
Let me know. Good Luck! I'm going from memory, I hope I didn't mistype anything. :o)

Resources