Comparing Maps by keySet - java

In the this oracle java it says that:
Along similar lines, suppose you want to know whether two Map objects
contain mappings for all of the same keys.
if (m1.keySet().equals(m2.keySet())) {
... }
I thoroughly understand that it works and also how it works. However would not be easier doing something like :
if (m1.equals(m2)){
..}
Or for other reasons that I am not seeing it's better using the collection view?
Thanks in advance.

Maps can have same keys but different values:
Map<String, String> m1 = new HashMap<>();
m1.put("x", "1");
m1.put("y", "2");
Map<String, String> m2 = new HashMap<>();
m2.put("x", "1");
m2.put("y", "4");
System.out.println(m1.equals(m2)); // false
System.out.println(m1.keySet().equals(m2.keySet())); // true

No, because the first way only checks that the key set is equal. That is logically different than determining whether two maps are the same.

The comparison m1.equals(m2) will check that both keys and values are equal between the two maps.
From the Java 7 javadoc:
boolean equals(Object o)
Compares the specified object with this map for equality. 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()). This ensures that the equals method works properly across different implementations of the Map interface.

Your second example checks something different than your first example.
In your first example, you only compare the keys of two Maps, ignoring the values associated with those keys.
In your second example, you compare both the keys and the corresponding values of two Maps.

It depends on how you are trying to define the equality of a map. If you want it to be based on keys you would use the first function:
if (m1.keySet().equals(m2.keySet())) { ... }
If however you are more interested in the values:
if (m1.values().equals(m2.values())) { ... }
And if you want to compare the full map:
if (m1.entrySet().equals(m2.entrySet())) { ... }

No, the contract in the Map interface specifies that equals() compares the map's entrySet(), which contains the keys and values. So, implementations of the Map interface (HashMap etc.) will abide by that contract.

Related

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

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.

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.

Compare two HashMaps keys for equality?

I have two HashMap<HashSet<String>, Long> that I want to compare based on the Key. The Key is a HashSet<String>, I might need to change to TreeSet<String> but I don't think it is necessary. How would I compare these?
NOTE: The Map is just used as a wrapper around a single Set.
for(HashMap<HashSet<String>, Long> entry : ListOfMaps) {
if(entry.keySet().equals(entry2.keySet())) {
// do something
}
}
I want to check the Set1.equals(Set2).
The set must be exactly the same. Since there is only one Set<String> in each HashMap<Set<String>, Long> it makes me nervous that I am grabbing all the Keys, or is this okay?
The equals() contract of Set says:
Returns true if the given object is also a set, the two sets have the same size, and every member of the given set is contained in this set. This ensures that the equals method works properly across different implementations of the Set interface.
So your code will work as long as the Set implementation follows the contract.
However, this may be dangerous depending on what your code does with the Sets that are used as keys. Once an object is used as a key in a Map, it shouldn't change because that breaks the contract of Map.
Map<Set<String>, T> map = HashMap<>();
Set<String> mySet = new HashSet<>();
mySet.add("first");
map.put(mySet, myValue);
Set<String> copyOfMySet = new HashSet<>(mySet);
//returns true
map.contains(copyOfMySet);
//modifying mySet
mySet.remove("first");
//this will now return false
map.contains(copyOfMySet);

Map containing itself as a value;

Directly from this java doc:
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.
Why would the hashcode and equals no longer be well defined on such a map?
The relevant part form AbstractMap.equals which is used by most Map implementations:
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key))) // would call equals on itself.
return false;
}
}
Adding the map as a value would result in an infinite loop.
The full quote of the paragraph from the Java Docs is:
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.
The AbstractMap.hashCode() method uses the hash code of the key value pairs in the map to compute a hash code. Therefore the hash code generated from this method would change every time the map is modified.
The hash code is used to compute the bucket to place a new entry. If the map was used as a key within itself then the computed bucket would be different everytime a new entry is updated/removed/modified. Therefore, future lookups with the map as a key will most likely fail because a differnt bucket is computed from the hash code. Future puts may not be able to detect that the key is already present in the map and then allow multiple entries that have the same key (but in different buckets)
Two maps are equal if the same keys map om the same values. (In some implementations.) So to check equality, the equality of every member should be checked.
Therefore, if a map contains itself, you would get an infinite recurssion of equality checks.
The same goes for hashes, as these can be calculated dependend on the hashes of the elements in the map.
Example:
Map<Int, Object> ma;
Map<Int, Object> mb;
Map<Int, Object> mc;
ma.put(1, ma);
ma.put(2, mb);
mc.put(1, ma);
mc.put(2, mb);
As a human, we can see ma and mc are equal from the definition. A computer would see 2 maps on mb (an empty map) in both maps, which is good. It would see 1 maps on another map in both mc and ma. It checks if these maps are equal. To determine this, it checks again if the two value for 1 are equals. And again.
Note that this is not the case for all implementations. Some implementations might check equality on the location in memory the object is saved, ... But every recursive check will loop infinitely.
To try to explain it:
The equals method will iterate over both Maps and call the equals method of each key and value of the map. So, if a map contains itself, you would keep calling the equals method indefinitely.
The same thing happens with the hash code.
Source: source code of the class AbstractMap

java hashtable with collision resolution

I want to get all the values(multiple) of a particular key.But i m getting only one value?I dont know how to print all the values.Great help if someone correct the code..did not get any help from google search..
import java.util.*;
public class hashing
{
public static void main(String args[])
{
String[] ary=new String[4];
String key;
char[] chrary;
ary[0]=new String("abcdef");
ary[1]=new String("defabc");
ary[2]=new String("ghijkl");
ary[3]=new String("jklghi");
Hashtable<String, String> hasht = new Hashtable<String, String>();
for(int i=0;i<4;i++){
chrary=ary[i].toCharArray();
Arrays.sort(chrary);
key=new String(chrary);
hasht.put(key,ary[i]);
}
Enumeration iterator = hasht.elements();
while(iterator.hasMoreElements()) {
String temp = (String)iterator.nextElement();
System.out.println(temp);
}
}
}
PS:output is defabc jklghi.I want abcdef defabc ghijkl jklghi.
Hashtables can only contain one value per key. To store multiple values, you should either
Store a collection (e.g. List<String> or array) per key. Note that you'll have to initialise the collection prior to insertion of the first value corresponding to that key
Use a MultiMap
Note that many MultiMap implementations exist. The Oracle docs provide a simple implementation too (see here, and search for MultiMap)
The way HashMaps work is that there is only one value for a given key. So if you call:
map.put(key, value1);
map.put(key, value2);
the second line will override the value corresponding to the key.
Regarding your comment about collision, it means something different. Internally, a HashMap stores the key/value pairs in buckets that are defined based on the hashcode of the key (hence the name: hashmap). In the (low probability if the hashcode function is good) case where two non-equal keys have the same hashcode, the implementation needs to make sure that querying the hashmap on one of those keys will return the correct value. That is where hash collision need to be handled.
That's not what collision resolution is meant to do. Collision resolution lets you handle the case when two object with different keys would go into the same "bucket" in the hash map. How this resolution happens is an internal detail of the hash map implementation, not something that would be exposed to you.
Actually, in your case, its not collision, its same key with same hashcode. In general Collision occurs only if two different keys generate same hashcode, This can occur due to a bad implementation of hashCode() method.
Yes, java.util.HashMap will handle hash collisions, If you look at the source code of HashMap, it stores each value in a LinkedList. That means, if two different keys with same hashcode comes in.. then both values will go into same bucket but as two different nodes in the linked list.
Found this link online, which explain How hash map works in detail.
if key is the same, the value will be updated. jvm will not put a new key/value for same keys...
Your Hashtable<String, String> maps one string to one string. So put replaces the value that was before linked to a specific key.
If you want multiple values, you can make a Hashtable<String, []String> or a Hashtable<String, List<String>>.
A cleaner solution would be to use Google's Multimap which allows to associate multiple values to one key :
A collection similar to a Map, but which may associate multiple values
with a single key. If you call put(K, V) twice, with the same key but
different values, the multimap contains mappings from the key to both
values.
You are only putting one String for each key:
hasht.put(key,ary[i]);
So if i=1 that means you put defabc, why do you expect to get multiple values for same key?
Hashtable, like all Map keep only one value per key, the last value you set.
If you want to keep all the values, just print the original array.
String[] ary = "abcdef,defabc,ghijkl,jklghi".split(",");
System.out.println(Arrays.toString(ary));
prints
[abcdef, defabc, ghijkl, jklghi]

Categories

Resources