Compare two HashMaps keys for equality? - java

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

Related

Void Class in HashMap as a Value object

I know the basic that a HasMap is a Key-Value pair but I want to have a HashMap with keys only(No Values)
I want to put below java snippet in my complex method(i.e HashMap with only Keys and no value associated to those Keys). My requirement is that i am processing a List of Duplicate Records, and during comparisons, I am keeping only one identifier value(from group of duplicates) in a HasMap which I can later compare that whether the system has already processed it or not.
Here is the code snippet(gives Compile time error as Void class is uninstantiable).
Map<Integer,Void> map=new HashMap<Integer, Void>();
//Some Logic goes here
map.put("ss",new Void());
Any suggestion/help to have a HasMap only Keys with no value are welcome.
Normally you would use a Set for such an issue, because there is no need to have a Key-Value structure when not using the value at all.
Correct Solution
Set<String> uniqueValues = new HashSet<String>();
uniqueValues.add( "a" );
uniqueValues.add( "a" );
assert uniqueValues.size() == 1;
Note this is just for completeness I would always use a Set for your requirement and the rest is more for fun/learning/confuse people:
Since Void has a private constructor so you can not create an instance with the new Keyword.
However there are at least two possibilities to put something in your Map.
Solution one is to add null as value. Because you do not need it anyway. And the second one would use reflection to ignore the private constructor of the Void class.
HACK SOLUTION
Map<String, Void> map = new HashMap<String,Void>();
Constructor<Void> constructor= (Constructor<Void>) Void.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Void voidObj = constructor.newInstance();
map.put( "a", voidObj );
map.put( "a", voidObj );
assert map.size() == 1;
If I understand correctly you want a list where you can add keys but it should not allow to add duplicate keys. Then the solution is to use a Set(Oracle Documentation):
Set<Integer> mySet = new TreeSet<Integer>();
Java also provides a Hashset(Oracle Documentation)
Set<Integer> mySet = new HashSet<Integer>();
You may also need you own Comparator.
Why not just use another list? If you really need to use a HashMap for whatever reason, you can just add null values instead of void.
Map<Integer,Object> map=new HashMap<Integer, Object>();
map.put("ss", null);
Please do not do this. A HashMap is a Map which is a Key-Value-pair. A Map without values is not a Map.
If you want to store values without duplicates use a Set - a HashSet for example.
First of all the constructor of Void class is private, so the compiler will mark new Void() as error. Next, to prevent duplicates, you could just use a Set . Why not go with HashSet?.
Here's what javadoc says about Void -->
The Void class is an uninstantiable placeholder class to hold a
reference to the Class object representing the Java keyword void.

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

Comparing Maps by keySet

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.

How to use hashmap.put without overriding the previous data? (java)

I have something along the lines of this:
public HashMap<Boolean, String> map = new HashMap();
map.put(this.inverted, "Inverted");
map.put(this.active, "Loading");
System.out.println(map.size());
after seeing that the size was always 1, I realised that using map.put was overriding the previous data. I am currently trying to iterate over the hashmap. Is there a way to add mappings to it without overriding previous ones?
You have declared your HashMap as: -
public HashMap<Boolean, String> map = new HashMap();
Now, just think how many maximum mapping can you have in your map? The answer you can get by thinking of, what all values can your Boolean type take. This is because, you cannot have duplicate keys in a HashMap.
So, probably you got it now, that you can at max have only 2 mappings in your map, one for true and other for false(In fact you can have a 3rd one too, as you can have a mapping for a null key too in your HashMap).
So, in your case, if both this.inverted and this.active are either true or false. Then only one of them can be there, and that would be the later value inserted.
Is there a way to add mappings to it without overriding previous ones?
Probably you have build your HashMap wrongly. You should declare your map as: -
private Map<String, Boolean> map = new HashMap();
And now you can put two mappings as: -
map.put("Inverted", this.inverted);
map.put("Loading", this.active);
It's because this.inverted.equals(this.active) and this.inverted.hashcode()==this.active.hashcode()
Maybe you need redefine the equals method for the key.
In MAP
An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value. ---> from Map Api
from your implementation, may be this.inverted and this.active both have same value.
Check the input once. print the keySet, then check.
or change the input to Map<String, Boolean>
As #Frank suggest you should invert your Map.
public final Map<String, Boolean> map = new HashMap<>();
map.put("Inverted", this.inverted);
map.put("Loading", this.active);
System.out.println(map);
If the keys are the same than the previous value is overwritten in a standard Java Map. If you don't want this, you can have a look at a multimap which is implemented for example in commons-collections. It can hold different values for one key.
Hashmap is based on key/value pairs. If your keys are equal (they have the same hashcode), it will behave as you described.
For your use case, reversing your key/value pairs will help you.
public HashMap<String, Boolean> map = new HashMap();
map.put("Inverted", this.inverted);
map.put("Loading", this.active);
System.out.println(map.size());
Get object of innermap ,by passing outer map key .. Then check if key of innermap exists then update values with previous data. else create new object of inner map.

which datastructure should i use based on my needs?

Needs:
Storing objects of a class which overrides equals and hash code
Will be looping and shoving objects into the datastructure
Need to be able to call contains to check whether a certain object is stored in the structure
If contains returns true then fetch that specific object from the structure and call a certain getter on that object
Options I've considered:
Map - this works for all the needs but I don't really have a map (key and a value). all I have is bunch of objects. Would it be a good practice to forcefully use a map by storing objects as key and integer or something in the value?
Set would work, however, it doesn't have a fetch method like get.
List would also work, but it doesn't have a method to fetch that is non index based. Meaning, once contains returns true I'll have to loop through the list to find the index of my particular object and then fetch it.
I'm open to using different libraries like apache commons or guava for example.
List would also work, but it doesn't have a method to fetch that is non index based.
List has an indexOf(Object) method which will do exactly what you want.
Although the best thing to use in this scenario would be a Map, because it offers fast retrieval based on Key-Value pair.
But List also allows to fetch data based on index.
So, you can use either a List or a Map. But to make your task easier, I would prefer a Map. Because i case of Map you won't have to search for an index of an Object, then get the Object at that index. Fetching is just a one-line operation.
// When using a List.
List<String> myList = new ArrayList<String>();
if (myList.contains("rohit")) {
myList.get(myList.indexOf("rohit"));
}
// When using Map.
Map<String, String> myMap = new HashMap<String, String>();
// You can directly fetch your object, based on some Key if you have one..
myMap.get("key");
You need a set. You don't need a fetch method (you think you do), because like you said you only have a bunch of objects. And since these use equals and hashCode, a set is exactly what you need.
Of course a map could do as well, because its keys is a set as well, but in the end you need to better specify your requirements, as it appears you are a bit confused as to the purpose of your data structure. From what I understand, you do not need a map indeed.
A hash set implementation will do. Here is what you can do with it all:
class Foo
{
final String name;
Foo(String name)
{
this.name = name;
}
boolean equals(Object obj)
{
return (obj instanceof Foo) && ((Foo)obj).name.equals(name);
}
}
Set<Foo> fooSet = new HashSet<Foo>();
fooSet.add(new Foo("someFoo"));
assert fooSet.contains(new Foo("someFoo"));

Categories

Resources