Why will HashMap ignore entries with keys from the same Superclass? - java

I have created a map with maps inside as a test and it seems that the outer map will ignore the rest of the maps. I assume it is overriding them. Also I presume that it is because they all extend from Map so it is treating them as the same instance when they are not.
Map<Map<Integer, String>,String> maps=new HashMap<>();
maps.put(new HashMap<Integer, String>(),"HashMap");
maps.put(new TreeMap<Integer, String>(),"TreeMap");
maps.put(new LinkedHashMap<Integer, String>(),"LinkedHashMap");
maps.put(new Hashtable<Integer, String>(),"Hashtable");
Printing the map to logs will show only the last one added:
maps: {{}=Hashtable}
I tried with other Map implementations and all have the same behavior except TreeMap which will throw an exception due to the fact that HashMap does not implement Comparable.
Is this expected behavior? Why does HashMap behave like this?

When we take a look at the documentation of Map::equals, we see that this method
Returns true if the given object is also a map and the two maps represent the same mappings. More formally, two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet()).
Due to the contract between Object::equals and Object::hashCode, this means that those maps will also have the same hash code. This, in return, means that they are, for Map::put, indistinguishable. Thus, each put(...) in the sample program overrides the previous put and thus results in a final map size of 1 with only the HashTable in the entrySet().
So yes, this is expected behaviour. And it is not only expected for HashMap, but for all Map implementations.
As was already pointed out in the comments by Pshemo, it is questionable to use a Map as key for another Map and, in general, we should use immutable objects as keys.

Related

Is there thing/things that I should do first before using customized types on hashmap?

I want to use a collection to host my data pairs, the order isn't important, duplicate values are allowed and I need fast operation/indexing, so I chose hashmap.
My question is, do I have to override something first before using it like this?
Hashmap<string, myOwnDataType> mhashmap = new Hashmap ();
For example, in TreeSet, I need to override the comparator in order to use my own customized data type, what preparations should I do in the case of Hashmap???
You're all set. String has its own comparator. But please add <> (to avoid a type safety warning) and use upper and lower case correctly:
HashMap<String, MyOwnDataType> mHashMap = new HashMap<>();
Two types are involved in a map: the type of the keys and the type of the values. To use your own class as the value type you don't have to do anything. This holds for HashMap as well as for TreeMap.
However, to use your own type as the key type in a HashMap you do have to consider the following two things:
A HashMap uses the methods hashCode() and equals() to find key-value pairs and to determine whether two keys are equal. Both methods are implemented in Object, but in most cases you will want to override hashCode() and equals() and make sure they are consistent with each other (that means that when a.equals(b) is true, then a.hashCode() == b.hashCode().
When an object is used as a key in the map, it cannot change anymore (with respect to hashCode() and equals(). Often, keys used in a hash map are immutable.
Strings, which you use in your example, can be used as keys in a HashMap without problems.

HashMap using Object as key

I made a post a few days ago about using a HashMap in a simple banking program, but I'm having issues with using Objects as keys.
HashMap <Account,Client> HM = new HashMap<Account, Client>();
HM.put(new Account(2193,"Uri"), new Client(2193,0,"Uri"));
HM.get(2193,"Uri");
Account and Client are classes in other parts of the source. My issue is that the HM.get isn't working as intended, and is giving me an error. Is there another way I'm to 'get' the value? Not sure how to use the key. Do note, the setup of the HashMap is without error.
Furthermore, is there a better way to go about this?
This will give you better idea. that why you need to override hashcode and equals method.
Why do I need to override the equals and hashCode methods in Java?
After overriding hashcode and equals method.
you need to use your object while getting data from hashMap.
HM.get(new Account(2193,"Uri"));
First of all this code does not compile as you are passing 2 arguments to get() which expects only 1 argument.
That argument is supposed to be the key you use in the map and has to be of the same type you declared while declaring your map, in your case HashMap <Account,Client> HM means that HM (which btw should be lowercase by convention) holds as keys objects of type Account and objects of type Client as values.
It would still compile if you did:
get(2193)
Since get() takes an Object but it would simply return a null.
You need to do get(new Account(2193,"Uri")).
Next you do not need to override equals and hashCode in those classes but it is highly recommended (others already pointed to links saying why). Also as per the doc you should make the keys immutable so they do not change, otherwise you might get strange behavior.
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. A special case of this prohibition is that
it is not permissible for a map to contain itself as a key. While it
is permissible for a map to contain itself as a value, extreme caution
is advised: the equals and hashCode methods are no longer well defined
on such a map.
For more detailed description of the Map interface follow Oracle's tutorial

Java - what is returned when two keys map to same value?

In Java, I understand if two keys maps to one value , linear chaining occurs due to collision.
For Example:
 Map myMap= new HashMap(); //Lets says both of them get mapped to same bucket-A and
myMap.put("John", "Sydney");//linear chaining has occured.
myMap.put("Mary","Mumbai"); //{key1=John}--->[val1=Sydney]--->[val2=Mumbai]
So when I do:
myMap.get("John"); // or myMap.get("Mary")
What does the JVM return since bucket-A contains two values?
Does it return the ref to "chain"? Does it return "Sydney"? Or does it return "Mumbai"?
Linear chaining happens when your keys have the same hashcode and not when two keys map to one value.
So when I do: myMap.get("John"); // or myMap.get("Mary")
map.get("John") gives you Sydney
map.get("Mary") gives you Mumbai
What does the JVM return since bucket-A contains two values?
If the same bucket contains two values, then the equals method of the key is used to determine the correct value to return.
It is worthwhile mentioning the worst-case scenario of storing (K,V) pairs all having the same hashCode for Key. Your hashmap degrades to a linked list in that scenario.
The hashCode of your method determines what 'bucket' (aka list, aka 'linear chain') it will be put in. The equals method determines which object will actually be picked from the 'bucket', in the case of collision. This is why its important to properly implement both methods on all object you intend to store in any kind of hash map.
Your keys are different.
First some terminology
key: the first parameter in the put
value: the second parameter in the put
entry: an Object that holds both the key & the value
When you put into a HashMap the map will call hashCode() on the key and work out which hash bucket the entry needs to go into. If there is something in this bucket already then a LinkedList is formed of entries in the bucket.
When you get from a HashMap the map will call hashCode() on the key and work out which hash bucket to get the entry from. If there is more than one entry in the bucket the the map will walk along the LinkedList until it finds an entry with a key that equals() the key supplied.
A map will always return the Object tied to that key, the value from the entry. Map performance degrades rapidly if hashCode() returns the same (or similar) values for different keys.
You need to use java generics, so your code should really read
Map<String, String> myMap = new HashMap<String, String>();
This will tell the map that you want it to store String keys and values.
From my understanding, the Map first resolves the correct bucket (identified by the hashcode of the key). If there's more than one key in the same bucket, the equals method is used to find the right value in the bucket.
Looking at your example what confuses you is that you think values are chained for a given key. In fact Map.Entry objects are chained for a given hashcode. The hashCode of the key gives you the bucked, then you look at the chained entries to find the one with the equal key.

Can i have HashSets as the keys in a HashMap? Suggest an alternative if not

Edit: explained the problem properly now.
I have a hashmap where i want to store sets of words seen together (key) and the lines in which they were seen together(value). This is the structure i came up with:
HashMap<HashSet<String>, HashSet<Integer>> hm= ...
for inputs:
mango, banana, apple
apple, banana
peach, walrus
walrus, peach
As I read this, line by line, I make new temporary keys (hashsets not yet inserted into hashmap) from the combination of words in the line. Each temporary key is a hashset of a subset of the words in the line. If a temporary key already exists in my hashmap, which i check by
if(hashmap.containsKey(hashset))
i simply add the new line to that key's corresponding value, if not, I make a new entry in the hashmap and take care of it.
At no point do i change an existing key. I only update their corresponding values in the hasmmap.
my hashmap, at the end of reading the file, should look something like this
[apple, banana]=[1,2]
[peach, walrus]=[3,4]
...
the problem is that the
if(hashmap.containsKey(hashset))
piece of code doesn't always detect existing keys. Why is this? Is this structure not allowed?
Thank you
This should work, but you need to watch out for mutability of the keys. If you ever change the contents of one of the keys, its hashcode will change, and your map will start doing strange things. From the javadoc for Map:
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. A special case of this prohibition is that
it is not permissible for a map to contain itself as a key. While it
is permissible for a map to contain itself as a value, extreme caution
is advised: the equals and hashCode methods are no longer well defined
on such a map.
To avoid this, wrap the keys with Collections.unmodifiableSet() immediately upon creation, or just use ImmutableSet from Guava.
You can, but once you have added a HashSet as a key to a HashMap you shouldn't modify it again, as the HashSet.hashCode() might change and you'll never find your HashSet again. In other words, if you're doing something like that, be sure that your keys are immutable HashSets (see also Matt's answer here)
An alternative is to use the MultiKeyMap along with a MultiKey from commons collections
The problem you have is well explained by #Lukas ans #Matt.
I think you could get away by using extending or using a decorator pattern to create a Hashset that overides equals and hashCode in a way that is independent of the contents.
This way you can avoid introducing dependencies on third party jars just for a specific problem

Efficient data structure with two keys

I have an Android app in which I use a HashMap to store container objects. During the course of the App, the datastructure is accessed continuously.
However, about half the time, the reference used in not the Key in the map but another variable from the object so I end up looping over the structure again and again.
Is there an efficient way to have a datastructure indexed on two keys in Java ?
Why not two maps with different keys, but that both refer to the same values?
Manage two maps, where two sets of keys map to the same underlying set of objects. Wrap them in a class that has methods similar to a normal map, but internally searches on both keys, and synchronizes additions and deletions.
This is efficient because manipulations are (in the worst case) linearly proportionate to managing a single map.
I'd create a key object that combines the two variables.
You could use one map with both keys:
Map<Object, Person> personMap = new HashMap<Object, Person>()
Person person = ...
personMap.put(person.getName(), person)
personMap.put(person.getSSN(), person)
Then you can retrieve by the key. This of course assumes that there are no collisions in your key usage. If your two keys are different class types, then this is safe to do. If your keys are the same type (example String), then you may not want to use the two maps solution.
Follow-up: This approach does suffer from losing type safety, but it only impacts put(K, V) and putAll(Map<? extends K, ? extends V>), as get(Object) and containsKey(Object) always accepts Object.
So with this limitation I'd wrap this single map or go with the two map solution (also wrapped).

Categories

Resources