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

Time:09-17

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

  1. puting the f1 User into friendship of user1 (owner of the friendship). The time when the f1.getId() == null
  2. 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)
  3. 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?

CodePudding user response:

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).

CodePudding user response:

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".

  • Related