Can we use object as a key in hashmap in Java? - java

How to use an object as a key in a hashmap. If you use an object as key do you need to override equals and hashcode methods for that object?

A simple thumb rule is to use immutable objects as keys in a HashMap.
because:
If it were mutable, then the hashcode() value or equals() condition might change, and you would never be able to retrieve the key from your HashMap.
More precisely, class fields that are used to compute equals() and hashcode() should be immutable!
Now, suppose you create your own class:
To compare two objects of your class you will have to override equals()
To use it as a key in any Hash based Data structure you will have to override hashcode() (again, keeping immutability in mind)
Remember that if two objects are equal(), then their hashcode() should be equal as well!

hashCode() -HashMap provides put(key, value) for storing and get(key) for retrieving values from a HashMap. When using put(key, value) to store a key-value-pair, HashMap calls hashcode() on the key object to calculate a hash that is used to find a bucket where the Entry object is stored. When get() is used to retrieve a value, again, the key object is used to calculate a hash which is used then to find a bucket where that particular key is stored.
equals() - equals() is used to compare objects for equality. In the case of HashMap, the key object is used for comparison, also using equals(). HashMap knows how to handle hashing collisions (more than one key having the same hash value, thus assigned to the same bucket). In that case objects are stored in a linked list (refer to the figure for more clarity).
hashCode() helps in finding the bucket where that key is stored, equals() helps in finding the right key as there may be more than one key-value pair stored in a single bucket.

You can use any object in a HashMap as long as it has properly defined hashCode and equals methods - those are absolutely crucial because the hashing mechanism depends on them.

Answer to your question is yes, objects of custom classes can be used as a key in a HashMap. But in order to retrieve the value object back from the map without failure, there are certain guidelines that need to be followed.
1)Custom class should follow the contract between hashCode() and equals().
The contract states that:
If two objects are equal according to the equals(Object) method, then calling
the hashCode method on each of the two objects must produce the same integer result.
This can be done by implementing hashcode() and equals() in your custom class.
2) Make custom class immutable.
Hint: use final, remove setters, use deep copy to set fields

package com.java.demo.map;
import java.util.HashMap;
public class TestMutableKey
{
public static void main(String[] args)
{
//Create a HashMap with mutable key
HashMap<Account, String> map = new HashMap<Account, String>();
//Create key 1
Account a1 = new Account(1);
a1.setHolderName("A_ONE");
//Create key 2
Account a2 = new Account(2);
a2.setHolderName("A_TWO");
//Put mutable key and value in map
map.put(a1, a1.getHolderName());
map.put(a2, a2.getHolderName());
//Change the keys state so hash map should be calculated again
a1.setHolderName("Defaulter");
a2.setHolderName("Bankrupt");
//Success !! We are able to get back the values
System.out.println(map.get(a1)); //Prints A_ONE
System.out.println(map.get(a2)); //Prints A_TWO
//Try with newly created key with same account number
Account a3 = new Account(1);
a3.setHolderName("A_THREE");
//Success !! We are still able to get back the value for account number 1
System.out.println(map.get(a3)); //Prints A_ONE
}
}

Yes, you should override equals and hashcode, for the proper functioning of the code otherwise you won't be able to get the value of the key which you have inserted in the map.
e.g
map.put(new Object() , "value") ;
when you want to get that value ,
map.get(new Object()) ; // This will always return null
Because with new Object() - new hashcode will be generated and it will not point to the expected bucket number on which value is saved, and if eventually bucket number comes to be same - it won't be able to match hashcode and even equals so it always return NULL .

Yes, we can use any object as key in a Map in java but we need to override the equals() and hashCode() methods of that object class. Please refer an example below, in which I am storing an object of Pair class as key in a hashMap with value type as string in map. I have overriden the hashCode() and equals() methods of Pair class. So, that different objects of Pair class with same values of Pair(x,y) will be treated as one object only.
import java.util.*;
import java.util.Map.Entry;
class App { // Case-sensitive
private class Pair {
private int x, y;
public Pair(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pair other = (Pair) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
public static void main(String[] args) {
App obj = new App();
obj.show();
}
private void show() {
Map<Pair, String> map = new HashMap<>();
Pair obj1 = new Pair(10, 20);
Pair obj2 = new Pair(40, 50);
Pair obj3 = new Pair(10, 20);
// We can see that obj1 and obj3 have same values. So, we want to store these
// objects
// as one .To achieve
// that,
// we have overridden equals() and hashCode() methods of Pair class.
map.put(obj1, "First");
map.put(obj2, "Second");
map.put(obj3, "Third");
System.out.printf("Size of Map is :%d \n", map.size());
for (Entry<App.Pair, String> p : map.entrySet()) {
Pair pair = p.getKey();
System.out.printf("Map key-value pair is (%d,%d)->%s \n", pair.x, pair.y, p.getValue());
}
// output -
// Size of Map is :2
// Map key-value pair is (10,20)->Third
// Map key-value pair is (40,50)->Second
}
}

Related

Compare hashMap key to ArrayList [duplicate]

I have this test code:
import java.util.*;
class MapEQ {
public static void main(String[] args) {
Map<ToDos, String> m = new HashMap<ToDos, String>();
ToDos t1 = new ToDos("Monday");
ToDos t2 = new ToDos("Monday");
ToDos t3 = new ToDos("Tuesday");
m.put(t1, "doLaundry");
m.put(t2, "payBills");
m.put(t3, "cleanAttic");
System.out.println(m.size());
} }
class ToDos{
String day;
ToDos(String d) { day = d; }
public boolean equals(Object o) {
return ((ToDos)o).day == this.day;
}
// public int hashCode() { return 9; }
}
When // public int hashCode() { return 9; } is uncommented m.size() returns 2, when it's left commented it returns three. Why?
HashMap uses hashCode(), == and equals() for entry lookup. The lookup sequence for a given key k is as follows:
Use k.hashCode() to determine which bucket the entry is stored, if any
If found, for each entry's key k1 in that bucket, if k == k1 || k.equals(k1), then return k1's entry
Any other outcomes, no corresponding entry
To demonstrate using an example, assume that we want to create a HashMap where keys are something which is 'logically equivalent' if they have same integer value, represented by AmbiguousInteger class. We then construct a HashMap, put in one entry, then attempt to override its value and retrieve value by key.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
}
HashMap<AmbiguousInteger, Integer> map = new HashMap<>();
// logically equivalent keys
AmbiguousInteger key1 = new AmbiguousInteger(1),
key2 = new AmbiguousInteger(1),
key3 = new AmbiguousInteger(1);
map.put(key1, 1); // put in value for entry '1'
map.put(key2, 2); // attempt to override value for entry '1'
System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(key3));
Expected: 2, 2, 2
Don't override hashCode() and equals(): by default Java generates different hashCode() values for different objects, so HashMap uses these values to map key1 and key2 into different buckets. key3 has no corresponding bucket so it has no value.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Output: 1, 2, null
Override hashCode() only: HashMap maps key1 and key2 into the same bucket, but they remain different entries due to both key1 == key2 and key1.equals(key2) checks fail, as by default equals() uses == check, and they refer to different instances. key3 fails both == and equals() checks against key1 and key2 and thus has no corresponding value.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Output: 1, 2, null
Override equals() only: HashMap maps all keys into different buckets because of default different hashCode(). == or equals() check is irrelevant here as HashMap never reaches the point where it needs to use them.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Actual: 1, 2, null
Override both hashCode() and equals(): HashMap maps key1, key2 and key3 into the same bucket. == checks fail when comparing different instances, but equals() checks pass as they all have the same value, and deemed 'logically equivalent' by our logic.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return value;
}
#Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 2, 2, 2
What if hashCode() is random?: HashMap will assign a different bucket for each operation, and thus you never find the same entry that you put in earlier.
class AmbiguousInteger {
private static int staticInt;
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return ++staticInt; // every subsequent call gets different value
}
#Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to no bucket, no corresponding value
map.get(key2); // map to no bucket, no corresponding value
map.get(key3); // map to no bucket, no corresponding value
Expected: 2, 2, 2
Actual: null, null, null
What if hashCode() is always the same?: HashMap maps all keys into one big bucket. In this case, your code is functionally correct, but the use of HashMap is practically redundant, as any retrieval would need to iterate through all entries in that single bucket in O(N) time (or O(logN) for Java 8), equivalent to the use of a List.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return 0;
}
#Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 2, 2, 2
And what if equals is always false?: == check passes when we compare the same instance with itself, but fails otherwise, equals checks always fails so key1, key2 and key3 are deemed to be 'logically different', and maps to different entries, though they are still in the same bucket due to same hashCode().
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return 0;
}
#Override
public boolean equals(Object obj) {
return false;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Actual: 1, 2, null
Okay what if equals is always true now?: you're basically saying that all objects are deemed 'logically equivalent' to another, so they all map to the same bucket (due to same hashCode()), same entry.
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
#Override
public int hashCode() {
return 0;
}
#Override
public boolean equals(Object obj) {
return true;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.put(new AmbiguousInteger(100), 100); // map to bucket 1, set as entry1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 100, 100, 100
You have overidden equals without overriding hashCode. You must ensure that for all cases where equals returns true for two objects, hashCode returns the same value. The hash code is a code that must be equal if two objects are equal (the converse need not be true). When you put your hard-coded value of 9 in, you satisfy the contract again.
In your hash map, equality is only tested within a hash bucket. Your two Monday objects should be equal, but because they are returning different hash codes, the equals method isn't even called to determine their equality - they are put straight into different buckets, and the possibility that they are equal isn't even considered.
I cannot emphasize enough that you should read Chapter 3 in Effective Java (warning: pdf link). In that chapter you will learn everything you need to know about overriding methods in Object, and in particular, about the equals contract. Josh Bloch has a great recipe for overriding the equals method that you should follow. And it will help you understand why you should be using equals and not == in your particular implementation of the equals method.
Hope this helps. PLEASE READ IT. (At least the first couple items... and then you will want to read the rest :-).
-Tom
When you don't override the hashCode() method, your ToDos class inherits the default hashCode() method from Object, which gives every object a distinct hash code. This means that t1 and t2 have two different hash codes, even though were you to compare them, they would be equal. Depending on the particular hashmap implementation, the map is free to store them separately (and this is in fact what happens).
When you do correctly override the hashCode() method to ensure that equal objects get equal hash codes, the hashmap is able to find the two equal objects and place them in the same hash bucket.
A better implementation would give objects that are not equal different hash codes, like this:
public int hashCode() {
return (day != null) ? day.hashCode() : 0;
}
when you comment, it returns 3;
because hashCode() inherited from the Object is ONLY called which returns 3 different hashcodes for the 3 ToDos objects. The unequal hashcodes means the 3 objects are destined to different buckets and equals() return false as they are the first entrant in their respective buckets.
If the hashCodes are different it is understood in advance that the objects are unequal.
They will go in different buckets.
when you uncomment, it returns 2;
because here the overridden hashCode() is called which returns the same value for all the ToDos and they all will have to go into one bucket, connected linearly.
Equal hashcodes dont promise anything about the equality or inequality of objects.
hashCode() for t3 is 9 and as it is the first entrant, equals() is false and t3 inserted in the bucket- say bucket0.
Then t2 getting the same hashCode() as 9 is destined for the same bucket0, a subsequent equals() on the already residing t3 in bucket0 returns false by the definition of overridden equal().
Now t1 with hashCode() as 9 is also destined for bucket0, and a subsequent equals() call returns true when compared with the pre-existing t2 in the same bucket. t1 fails to enter the map.
So the net size of map is 2 -> {ToDos#9=cleanAttic, ToDos#9=payBills}
This explains the importance of implementing both equals() and hashCode(), and in such a way that the fields taken up in determining equals() must also be taken when determining hashCode().
This will guarantee that if two objects are equal they will always have same hashCodes. hashCodes should not be perceived as pseudo-random numbers as they must be consistent with equals()
According to Effective Java,
Always override hashCode() when you override equals()
well, why? Simple, because different objects (content, not references) should get different hash codes; on the other hand, equal objects should get the same hash code.
According to above, Java associative data structures compare the results obtained by equals() and hashCode() invokations to create the buckets. If both are the same, objects are equals; otherwise not.
In the specific case (i.e. the one presented above), when hashCode() is commented, a random number is generated for each instance (behaviour inherited by Object) as hash, the equals() checks String's references (remember Java String Pool), so the equals() should return true but the hashCode() not, the result is 3 different objects stored.
Let's see what happens in case the hashCode() respecting the contract but returning always 9 is uncommented. Well, hashCode() is constantly the same, the equals() returns true for the two Strings in the Pool (i.e. "Monday"), and for them the bucket will be the same resulting in only 2 elements stored.
Therefore, it's definitely needed to be careful in using the hashCode() and equals() overriding, in particular when compound data types are user defined and they are used with Java associative data structures.
When hashCode is uncommented, HashMap sees t1 and t2 as being the same thing; thus, t2's value clobbers that of t1. To understand how this works, note that when hashCode returns the same thing for two instances, they end up going to the same HashMap bucket. When you try to insert a second thing into the same bucket (in this case t2 is being inserted when t1 is already present), HashMap scans the bucket for another key that is equals. In your case, t1 and t2 are equals because they have the same day. At that point, "payBills" clobbers "doLaundry". As for whether t2 clobbers t1 as the key, I believe this is undefined; thus, either behavior is allowed.
There are a few important things to think about here:
Are two ToDos instances really equal just because they have the same day of the week?
Whenever you implement equals, you should implement hashCode so that any two objects that are equals also have the same hashCode values. This is a fundamental assumption that HashMap makes. This is probably also true of anything else that relies the hashCode method.
Design your hashCode method so that the hash codes are evenly distributed; otherwise, you won't get the performance benefits of hashing. From this perspective, returning 9 is one of the worst things you can do.
Rather than thinking of hashCode in terms of hash-bucket mapping, I think it's more helpful to think somewhat more abstractly: an observation that two objects have different hash codes constitutes an observation that the objects are not equal. As a consequence of that, an observation that none of the objects in a collection have a particular hash code constitutes an observation that none of the objects in a collection are equal to any object which has that hash code. Further, an observation that none of the objects in a collection have a hash code with some trait constitutes an observation that none of them are equal to any object which does.
Hash tables generally work by defining a family of traits, exactly one of of which will be applicable to each object's hash code (e.g. "being congruent to 0 mod 47", "being congruent to 1 mod 47", etc.), and then having a collection of objects with each trait. If one is then given an object and can determine which trait applies to it, one can know that it must be in a collection of things with that trait.
That hash tables generally use a sequence of numbered buckets is an implementation detail; what is essential is that an object's hash code is quickly used to identify many things which it cannot possibly be equal to, and with which it thus will not have to be compared.
Whenever you create a new object in Java, it will be assigned a unique hashcode by JVM itself. If you wouldn't override hashcode method then object will get unique hascode and hence a unique bucket (Imagine bucket is nothing but a place in memory where JVM will go to find an object).
(you can check uniqueness of an hashcode by calling hashcode method on each object and printing their values on console)
In your case when you are un commentting hashcode method, hashmap firstly look for bucket having same hashcode that method returns. And everytime you are returning same hashcode. Now when hashmap finds that bucket, it will compare current object with the object residing into bucket using euqals method. Here it finds "Monday" and so hashmap implementation do not allow to add it again because there is already an object having same hashcode and same euqality implementation.
When you comment hashcode method, JVM simply returns different hashcode for all the three objects and hence it never even bother about comapring objects using equals method. And so there will be three different objects in Map added by hashmap implementation.

Why are keys immutable in Java?

Apologies for the fairly naive question, but I believe my own answer to be naive. I think keys (in HashTables) are immutable because we wouldn't want to somehow accidentally alter a key and therefore mess with the sorting of the HashTable. Is this a correct explanation? If so, how can it be more correct?
During the HashTable.put the key is hashed and it an its value are stored in one of a number of buckets (which are lists of key value pairs) based on the hash, for example something like:
bucket[key.hashcode() % numberOfBuckets].add(key, value)
If the key's hashcode changes after insertion it could then be in the wrong bucket and you would then not be able to find it and the hashtable would incorrectly return null on any get for that key.
Aside: Understanding the inner workings of a hashtable helps you understand the importance of a good quality hashcode function for your keys. As a poor hashcode function could result in a poor distribution of keys in buckets. And as buckets are just lists, this results in a lot of linear searches which greatly reduces the effectiveness of the hashtable. e.g. this terrible hashcode function puts everything in one bucket, so it's effectively just one list.
public int hashcode { return 42; /*terrible hashcode example, don't use!*/ }
This is also one reason why prime numbers appear in good hashcode functions, e.g.:
public int hashcode {
int hash = field1.hashcode();
hash = hash*31 + field2.hashcode(); //note the prime 31
hash = hash*31 + field3.hashcode();
return hash;
}
The general idea is correct, but not its details.
Keys in a HashTable need not be immutable, it's the result of a call to their hashCode() (and equals) method that needs to stay immutable and consistent (for the hash table to behave predictably, that is).
From a high-level point of view, it's because of the way hash tables work : when a (key, value) pair is inserted, the key's hashCode is used internally to figure out a "bucket" where the value will be put. And when the value is retrieved by key, the hashCode is computed once more, to find the bucket back.
Now if at any point in time between the insertion and the retreival, the result of calling hashCode changes, the "lookup bucket" will be different than the "insertion" bucket, and things will not behave predictably.
To sum up, given a Key object that looks like this (two internal Strings compose the objet, but only one, partOfHashCode is taken into account in hashCode / equals) :
public static class Key {
private String partOfHashCode;
private String notPartOfHashCode;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((partOfHashCode == null) ? 0 : partOfHashCode.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (partOfHashCode == null) {
if (other.partOfHashCode != null)
return false;
} else if (!partOfHashCode.equals(other.partOfHashCode))
return false;
return true;
}
}
The it is fine to use it this way :
public static void main(String[] args) {
Map<Key, String> myMap = new HashMap<>();
Key key = new Key();
key.partOfHashCode = "myHash";
myMap.put(key, "value");
key.notPartOfHashCode = "mutation of the key, but not of its hash/equals definition";
System.out.println(myMap.get(key));
}
(This logs the "value" object in the console).
But it is NOT fine to use it this way
public static void main(String[] args) {
Map<Key, String> myMap = new HashMap<>();
Key key = new Key();
key.partOfHashCode = "myHash";
myMap.put(key, "value");
key.partOfHashCode = "mutation of the hashCode of the key";
System.out.println(myMap.get(key));
}
(This last example could log "null" in the console).
For more on this subject, you should read also on hashCode / equals consistency.
There is no inherent guarantee in Java that HashTable-keys are immutable. It is not even guaranteed that their hashcode remains the same. But if you add keys that have a mutable hashCode you are in trouble. Assume you are inserting a key with a hashCode of 1. Then it is inserted in a hash-bucket corresponding to 1. Then alter the object to have a hashCode of 2 and call hashMap.get(key). While the object is still in the hashTable the system will look in the bucket corresponding to 2, but won't find it there. You won't even be able to remove the entry since it won't be found.
tl;dr For your application to work properly HashTable-keys need to have immutable hashcodes, but you have to take care of that fact for yourself.

HashMap is not adding duplicate keys

import java.util.*;
class U {
int x;
U(int x) {
this.x = x;
}
}
public class G {
public U a = new U(22);
public U b = new U(23);
Integer y = 22;
Integer r = 23;
void a() {
Map<U, Integer> set = new HashMap<U, Integer>();
set.put(a, y);
set.put(a, r);
set.put(b, y);
System.out.print(set.size() + " ");
}
public static void main(String[] args) {
G m = new G();
m.a();
}
}
I always get confused in Maps and Lists.
I know that when map put keys in the collection , it calls hashcode and if the bucket is same , equal method is called. However , I learned that if the class override these two methods then only duplicate keys are not stored. For example wrapper class : String implements its own hashcode and equal method. Moreover, if you don't do so, a unique hashcode is been called and duplicate keys get stored in the collection.
But in the above example , class U is NOT implementing hashcode and equal method. However , Map is not allowing duplicate keys.
I checked the SIZE : its 2
its supposed to be 3 because my U class is not implementing either hashcode nor equal.
please clear me
Thanks in advance
HashMap doesn't allow duplicated keys,
If you don't provide hashcode() and equals() implementation it extends it from super class (for your case it is java.lang.Object), and that implementation provides same hashcode for same object and equals() on same object returns true
public boolean equals(Object obj) {
return (this == obj);
}
as well
You are using the same instance of U as a key twice:
set.put(a, y);
set.put(a, r);
Your U class does not implement hashCode() as you mention, so the default implementation is Object#hashCode which will obviously be the same since it is the same instance. Therefore the map will only contain the second entry. However, if you try the following you would end up with two separate entries:
set.put(new U(22), y);
set.put(new U(22), r);
But generally you will always want to implement equals() and hashCode() for any class used as the key of a map - otherwise you can't look up the value without having access to the exact instance it was stored as!
By design hashmaps do not add duplicate keys. It will replace the value of the current item in the map with that key. See http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put%28K,%20V%29
If you want to add duplicate keys, try something like this:
Map<Integer, List<Integer>> map = new HashMap<Integer, List<Integer>>();
map.put(1, new ArrayList<Integer>());
map.get(1).add(1);
map.get(1).add(2);

Understanding the Implementation of HashTable in Java

I am trying to understand the implementation HashTables in Java. Below is my code:
Hashtable<Integer, String> hTab = new Hashtable<Integer, String>();
hTab.put(1, "A");
hTab.put(1, "B");
hTab.put(2, "C");
hTab.put(3, "D");
Iterator<Map.Entry<Integer, String>> itr = hTab.entrySet().iterator();
Entry<Integer, String> entry;
while(itr.hasNext()){
entry = itr.next();
System.out.println(entry.getValue());
}
When I run it, I get the below output:
D
C
B
Which means that there has been a collision for the Key = 1; and as per the implementation:
"Whenever a collision happens in the hashTable, a new node is created in the linkedList corresponding for the particular bucket and the EntrySet(Key, Value) pairs are stored as nodes in the list, the new value is inserted in the beginning of the list for the particular bucket". And I completely agree to this implementation.
But if this is true, then where did "A" go when I try to retrieve the entrysets from the hashTable?
Again, I tried with the below code to understand this by implementing my own HashCode and equals method. And surprisingly, this works perfect and as per the HashTable implementation. Below is my code:
public class Hash {
private int key;
public Hash(int key){
this.key = key;
}
public int hashCode(){
return key;
}
public boolean equals(Hash o){
return this.key == o.key;
}
}
public class HashTable1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Hashtable<Hash, String> hTab = new Hashtable<Hash, String>();
hTab.put(new Hash(1), "A");
hTab.put(new Hash(1), "B");
hTab.put(new Hash(2), "C");
hTab.put(new Hash(3), "D");
Iterator<Map.Entry<Hash, String>> itr = hTab.entrySet().iterator();
Entry<Hash, String> entry;
while(itr.hasNext()){
entry = itr.next();
System.out.println(entry.getValue());
}
}
}
Output :
D
C
B
A
Which is perfect. I am not able to understand this ambiguity in the behavior of HashTable in Java.
Update
#garrytan and #Brian: thanks for responding. But I still have a small doubt.
In my second code, where it works fine. I have created two objects which are new keys and since they are 2 objects, Key collision does not happens in this case and it works fine. I agree with your explanation. However, if in the first set of code I use "new Integer(1)" instead of simply "1", it still doesn't work although now I am creating 2 objects now and they should be different. I cross checked by writing the simple line below:
Integer int1 = new Integer(1);
Integer int2 = new Integer(1);
System.out.println(int1 == int2);
which gives "False". it means now, the Key collision should have been resolved. But still it doesn't work. Why is this?
By design hashtable is not meant to store duplicate keys.
I think you get mixed up between 'hash collision' and 'key collision'. Put it simply, hash table consist of a collection of linked lists (ie: buckets). When you add a new key value pairs (KVPs), it is distributed into the buckets by the key's hash value. 'hash collision' happen when two keys result in the same hash (hence they get put into the same bucket)
A good hash function is one that distributes the key evenly into a number of buckets, hence improving key searching performance.
The second example gives the behaviour you want because your implementation of equals is incorrect.
The signature is
public boolean equals(Object o) {}
not
public boolean equals(Hash h) {}
So what you have created is a hash Collision, where two objects have the same hash code (key), but they are not equal according to the equals method (because your signature is wrong, it's still using the == operator and not your this.key == h.key code). As opposed to a key collision, where the objects both have the same hashCode and are also equals, as in your first example. If you fix the code in the second example to implement the actual equals(Object o) method you will see 'A' will again be missing from the values.
In your second example you are not overriding the original equals function because you use the following signature:
public boolean equals(Hash h) {}
Thus the original equals function with Object as a parameter is still used and as you create a new object Hash for each insert that Object is different from the other one and thus your keys for A and B are not equal.
Furthermore a HashTable is designed to have ONE value for EACH key. And keys are indeed relying on the equals functions to be compared.
About your example with two new Integers, try comparing them with .equals(). You could also override the hashCode function to generate different hashCodes or not for each object, i.e. depending on time, but that would be not a good coding principle. Objects which are the same should hash to the same code.

Java HashSet vs HashMap

I understand that HashSet is based on HashMap implementation but is used when you need unique set of elements. So why in the next code when putting same objects into the map and set we have size of both collections equals to 1? Shouldn't map size be 2? Because if size of both collection is equal I don't see any difference of using this two collections.
Set testSet = new HashSet<SimpleObject>();
Map testMap = new HashMap<Integer, SimpleObject>();
SimpleObject simpleObject1 = new SimpleObject("Igor", 1);
SimpleObject simplObject2 = new SimpleObject("Igor", 1);
testSet.add(simpleObject1);
testSet.add(simplObject2);
Integer key = new Integer(10);
testMap.put(key, simpleObject1);
testMap.put(key, simplObject2);
System.out.println(testSet.size());
System.out.println(testMap.size());
The output is 1 and 1.
SimpleObject code
public class SimpleObject {
private String dataField1;
private int dataField2;
public SimpleObject(){}
public SimpleObject(String data1, int data2){
this.dataField1 = data1;
this.dataField2 = data2;
}
public String getDataField1() {
return dataField1;
}
public int getDataField2() {
return dataField2;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((dataField1 == null) ? 0 : dataField1.hashCode());
result = prime * result + dataField2;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SimpleObject other = (SimpleObject) obj;
if (dataField1 == null) {
if (other.dataField1 != null)
return false;
} else if (!dataField1.equals(other.dataField1))
return false;
if (dataField2 != other.dataField2)
return false;
return true;
}
}
The map holds unique keys. When you invoke put with a key that exists in the map, the object under that key is replaced with the new object. Hence the size 1.
The difference between the two should be obvious:
in a Map you store key-value pairs
in a Set you store only the keys
In fact, a HashSet has a HashMap field, and whenever add(obj) is invoked, the put method is invoked on the underlying map map.put(obj, DUMMY) - where the dummy object is a private static final Object DUMMY = new Object(). So the map is populated with your object as key, and a value that is of no interest.
A key in a Map can only map to a single value. So the second time you put in to the map with the same key, it overwrites the first entry.
In case of the HashSet, adding the same object will be more or less a no-op. In case of a HashMap, putting a new key,value pair with an existing key will overwrite the existing value to set a new value for that key. Below I've added equals() checks to your code:
SimpleObject simpleObject1 = new SimpleObject("Igor", 1);
SimpleObject simplObject2 = new SimpleObject("Igor", 1);
//If the below prints true, the 2nd add will not add anything
System.out.println("Are the objects equal? " , (simpleObject1.equals(simpleObject2));
testSet.add(simpleObject1);
testSet.add(simplObject2);
Integer key = new Integer(10);
//This is a no-brainer as you've the exact same key, but lets keep it consistent
//If this returns true, the 2nd put will overwrite the 1st key-value pair.
testMap.put(key, simpleObject1);
testMap.put(key, simplObject2);
System.out.println("Are the keys equal? ", (key.equals(key));
System.out.println(testSet.size());
System.out.println(testMap.size());
I just wanted to add to these great answers, the answer to your last dilemma. You wanted to know what is the difference between these two collections, if they are returning the same size after your insertion. Well, you can't really see the difference here, because you are inserting two values in the map with the same key, and hence changing the first value with the second. You would see the real difference (among the others) should you have inserted the same value in the map, but with the different key. Then, you would see that you can have duplicate values in the map, but you can't have duplicate keys, and in the set you can't have duplicate values. This is the main difference here.
Answer is simple because it is nature of HashSets.
HashSet uses internally HashMap with dummy object named PRESENT as value and KEY of this hashmap will be your object.
hash(simpleObject1) and hash(simplObject2) will return the same int. So?
When you add simpleObject1 to hashset it will put this to its internal hashmap with simpleObject1 as a key. Then when you add(simplObject2) you will get false because it is available in the internal hashmap already as key.
As a little extra info, HashSet use effectively hashing function to provide O(1) performance by using object's equals() and hashCode() contract. That's why hashset does not allow "null" which cannot be implemented equals() and hashCode() to non-object.
I think the major difference is,
HashSet is stable in the sense, it doesn't replace duplicate value (if found after inserting first unique key, just discard all future duplicates), and HashMap will make the effort to replace old with new duplicate value. So there must be overhead in HashMap of inserting new duplicate item.
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element.
This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the buckets. Iterating over this set requires time proportional to the sum of the HashSet instance's size (the number of elements) plus the "capacity" of the backing HashMap instance (the number of buckets). Thus, it's very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.
Note that this implementation is not synchronized. If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the set. If no such object exists, the set should be "wrapped" using the Collections.synchronizedSet method. This is best done at creation time, to prevent accidental unsynchronized access to the set
More Details

Categories

Resources