Understanding the workings of equals and hashCode in a HashMap - java

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.

Related

TreeSet Comparator failed to remove duplicates in some cases?

I have the following comparator for my TreeSet:
public class Obj {
public int id;
public String value;
public Obj(int id, String value) {
this.id = id;
this.value = value;
}
public String toString() {
return "(" + id + value + ")";
}
}
Obj obja = new Obj(1, "a");
Obj objb = new Obj(1, "b");
Obj objc = new Obj(2, "c");
Obj objd = new Obj(2, "a");
Set<Obj> set = new TreeSet<>((a, b) -> {
System.out.println("Comparing " + a + " and " + b);
int result = a.value.compareTo(b.value);
if (a.id == b.id) {
return 0;
}
return result == 0 ? Integer.compare(a.id, b.id) : result;
});
set.addAll(Arrays.asList(obja, objb, objc, objd));
System.out.println(set);
It prints out [(1a), (2c)], which removed the duplicates.
But when I changed the last Integer.compare to Integer.compare(b.id, a.id) (i.e. switched the positions of a and b), it prints out [(2a), (1a), (2c)]. Clearly the same id 2 appeared twice.
How do you fix the comparator to always remove the duplicates based on ids and sort the ordered set based on value (ascending) then id (descending)?
You're askimg:
How do you fix the comparator to always remove the duplicates based on ids and sort the ordered set based on value (ascending) then id (descending)?
You want the comparator to
remove duplicates based on Obj.id
sort the set by Obj.value and Obj.id
Requirement 1) results in
Function<Obj, Integer> byId = o -> o.id;
Set<Obj> setById = new TreeSet<>(Comparator.comparing(byId));
Requirement 2) results in
Function<Obj, String> byValue = o -> o.value;
Comparator<Obj> sortingComparator = Comparator.comparing(byValue).thenComparing(Comparator.comparing(byId).reversed());
Set<Obj> setByValueAndId = new TreeSet<>(sortingComparator);
Let's have a look on the JavaDoc of TreeSet. It says:
Note that the ordering maintained by a set [...] must be consistent with equals if it is to
correctly implement the Set interface. This is so
because the Set interface is defined in terms of the equals operation,
but a TreeSet instance performs all element comparisons using its
compareTo (or compare) method, so two elements that are deemed equal
by this method are, from the standpoint of the set, equal.
The set will be ordered according to the comparator but its elements are also compared for equality using the comparator.
As far as I can see there is no way to define a Comparator which satisfies both requirements. Since a TreeSet is in the first place a Set requirement 1) has to match. To achieve requirement 2) you can create a second TreeSet:
Set<Obj> setByValueAndId = new TreeSet<>(sortingComparator);
setByValueAndId.addAll(setById);
Or if you don't need the set itself but to process the elements in the desired order you can use a Stream:
Consumer<Obj> consumer = <your consumer>;
setById.stream().sorted(sortingComparator).forEach(consumer);
BTW:
While it's possible to sort the elements of a Stream according to a given Comparator there is no distinct method taking a Comparator to remove duplicates according to it.
EDIT:
You have two different tasks: 1. duplicate removal, 2. sorting. One Comparator cannot solve both tasks. So what alternatives are there?
You can override equals and hashCode on Obj. Then a HashSet or a Stream can be used to remove duplicates.
For the sorting you still need a Comparator (as shown above). Implementing Comparable just for sorting would result in an ordering which is not "consistent with equals" according to Comparable JavaDoc.
Since a Stream can solve both tasks, it would be my choice. First we override hashCode and equals to identify duplicates by id:
public int hashCode() {
return Integer.hashCode(id);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Obj other = (Obj) obj;
if (id != other.id)
return false;
return true;
}
Now we can use a Stream:
// instantiating one additional Obj and reusing those from the question
Obj obj3a = new Obj(3, "a");
// reusing sortingComparator from the code above
Set<Obj> set = Stream.of(obja, objb, objc, objd, obj3a)
.distinct()
.sorted(sortingComparator)
.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println(set); // [(3a), (1a), (2c)]
The returned LinkedHashSet has the semantics of a Set but it also preserved the ordering of sortingComparator.
EDIT (answering the questions from comments)
Q: Why it didn't finish the job correctly?
See it for yourself. Change the last line of your Comparator like follows
int r = result == 0 ? Integer.compare(a.id, b.id) : result;
System.out.println(String.format("a: %s / b: %s / result: %s -> %s", a.id, b.id, result, r));
return r;
Run the code once and then switch the operands of Integer.compare. The switch results in a different comparing path. The difference is when (2a) and (1a) are compared.
In the first run (2a) is greater than (1a) so it's compared with the next entry (2c). This results in equality - a duplicate is found.
In the second run (2a) is smaller than (1a). Thus (2a) would be compared as next with a previous entry. But (1a) is already the smallest entry and there is no previous one. Hence no duplicate is found for (2a) and it's added to the set.
Q: You said one comparator can't finish two tasks, my 1st comparators in fact did both tasks correctly.
Yes - but only for the given example. Add Obj obj3a to the set as I did and run your code. The returned sorted set is:
[(1a), (3a), (2c)]
This violates your requirement to sort for equal values descending by id. Now it's ascending by id. Run my code and it returns the right order, as shown above.
Struggling with a Comparator a time ago I got the following comment: "... it’s a great exercise, demonstrating how tricky manual comparator implementations can be ..." (source)

Treemap's headmap method in Java

I was checking headMap method of TreeMap which returns a portion of Map whose keys are strictly less than toKey. So I was expecting output to be B, C but it returns only B. Here is a thing I did weird I changed compareTo method like this return this.priority > o.priority ? 1 : -1; then it started returning C, B which is I was expecting. I am sure this is not correct but how can I get both B, C which has lower priority than A. Where I am getting it wrong. Thanks.
NavigableMap<PolicyTypePriorityWrapper, String> treeMap = new TreeMap();
PolicyTypePriorityWrapper a = new PolicyTypePriorityWrapper("A", 2);
PolicyTypePriorityWrapper b = new PolicyTypePriorityWrapper("B", 1);
PolicyTypePriorityWrapper c = new PolicyTypePriorityWrapper("C", 1);
treeMap.put(a, "A");
treeMap.put(b, "B");
treeMap.put(c, "C");
NavigableMap<PolicyTypePriorityWrapper, String> map = treeMap.headMap(a, false);
Set<PolicyTypePriorityWrapper> policyTypePriorityWrappers = map.keySet();
for (PolicyTypePriorityWrapper pol: policyTypePriorityWrappers) {
System.out.println(pol.getPolicyType());
}
PolicyTypePriorityWrapper.java
class PolicyTypePriorityWrapper implements Comparable<PolicyTypePriorityWrapper> {
private String policyType;
private int priority;
public PolicyTypePriorityWrapper(final String policyType, final int priority) {
this.policyType = policyType;
this.priority = priority;
}
public String getPolicyType() {
return this.policyType;
}
public int getPriority() {
return this.priority;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PolicyTypePriorityWrapper that = (PolicyTypePriorityWrapper) o;
if (priority != that.priority) return false;
return policyType.equals(that.policyType);
}
#Override
public int hashCode() {
int result = policyType.hashCode();
result = 31 * result + priority;
return result;
}
#Override
public int compareTo(final PolicyTypePriorityWrapper o) {
return Integer.compare(this.priority, o.priority);
}
}
That's because you are not following JDK documentation guidelines, from Comprarable:
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.
As you can see you have circumstances in which a.compareTo(b) == 0 but !a.equals(b). Both "B", 1 and "C", 1 are considered equal for the TreeMap:
Note that the ordering maintained by a tree map, like any sorted map, and whether or not an explicit comparator is provided, must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a sorted map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.
For example, if one adds two keys a and b such that (!a.equals(b) && a.compareTo(b) == 0) to a sorted set that does not use an explicit comparator, the second add operation returns false (and the size of the sorted set does not increase) because a and b are equivalent from the sorted set's perspective.
So what happens is that you compareTo is not able to distinguish two elements with same priority but different type, but since a TreeMap is using ONLY that method to decide if two elments are equal then you are not adding them both to the map in the first place.
Did you try if treeMap.size() == 3? My guess is that it's 2 in the first place.
The new PolicyTypePriorityWrapper("B", 1) is not qualified because it does it even not make it into the treeMap.
Why? Because the keys are the PolicyTypePriorityWrapper objects which are compared according to their integer priority value. Since b and c have the same priority, only the last one is saved to the treeMap. Compared a, b and c has a lower priority than a and equal to b. The key remains and the value is replaced. So in the map appears an entry PolicyTypePriorityWrapper b with the newly replaced value C.
It's the behavior of Map::put(K key, V value) method.
If the map previously contained a mapping for the key, the old value is replaced by the specified value.
Now the NavigableMap::headMap(K toKey, boolean inclusive) which returns a view of the portion of this map whose keys are less than (or equal to, if inclusive is true) toKey (taken from the documentation). The result is obvious. Only a and b stay in the treeMap, so the a is filtered out since it has smaller priority to b and only b is qualified to be returned.

Is different hashcode ensures a different bucket will be assigned in a hashmap? [duplicate]

As per my understanding I think:
It is perfectly legal for two objects to have the same hashcode.
If two objects are equal (using the equals() method) then they have the same hashcode.
If two objects are not equal then they cannot have the same hashcode
Am I correct?
Now if am correct, I have the following question:
The HashMap internally uses the hashcode of the object. So if two objects can have the same hashcode, then how can the HashMap track which key it uses?
Can someone explain how the HashMap internally uses the hashcode of the object?
A hashmap works like this (this is a little bit simplified, but it illustrates the basic mechanism):
It has a number of "buckets" which it uses to store key-value pairs in. Each bucket has a unique number - that's what identifies the bucket. When you put a key-value pair into the map, the hashmap will look at the hash code of the key, and store the pair in the bucket of which the identifier is the hash code of the key. For example: The hash code of the key is 235 -> the pair is stored in bucket number 235. (Note that one bucket can store more then one key-value pair).
When you lookup a value in the hashmap, by giving it a key, it will first look at the hash code of the key that you gave. The hashmap will then look into the corresponding bucket, and then it will compare the key that you gave with the keys of all pairs in the bucket, by comparing them with equals().
Now you can see how this is very efficient for looking up key-value pairs in a map: by the hash code of the key the hashmap immediately knows in which bucket to look, so that it only has to test against what's in that bucket.
Looking at the above mechanism, you can also see what requirements are necessary on the hashCode() and equals() methods of keys:
If two keys are the same (equals() returns true when you compare them), their hashCode() method must return the same number. If keys violate this, then keys that are equal might be stored in different buckets, and the hashmap would not be able to find key-value pairs (because it's going to look in the same bucket).
If two keys are different, then it doesn't matter if their hash codes are the same or not. They will be stored in the same bucket if their hash codes are the same, and in this case, the hashmap will use equals() to tell them apart.
Your third assertion is incorrect.
It's perfectly legal for two unequal objects to have the same hash code. It's used by HashMap as a "first pass filter" so that the map can quickly find possible entries with the specified key. The keys with the same hash code are then tested for equality with the specified key.
You wouldn't want a requirement that two unequal objects couldn't have the same hash code, as otherwise that would limit you to 232 possible objects. (It would also mean that different types couldn't even use an object's fields to generate hash codes, as other classes could generate the same hash.)
HashMap is an array of Entry objects.
Consider HashMap as just an array of objects.
Have a look at what this Object is:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
…
}
Each Entry object represents a key-value pair. The field next refers to another Entry object if a bucket has more than one Entry.
Sometimes it might happen that hash codes for 2 different objects are the same. In this case, two objects will be saved in one bucket and will be presented as a linked list.
The entry point is the more recently added object. This object refers to another object with the next field and so on. The last entry refers to null.
When you create a HashMap with the default constructor
HashMap hashMap = new HashMap();
The array is created with size 16 and default 0.75 load balance.
Adding a new key-value pair
Calculate hashcode for the key
Calculate position hash % (arrayLength-1) where element should be placed (bucket number)
If you try to add a value with a key which has already been saved in HashMap, then value gets overwritten.
Otherwise element is added to the bucket.
If the bucket already has at least one element, a new one gets added and placed in the first position of the bucket. Its next field refers to the old element.
Deletion
Calculate hashcode for the given key
Calculate bucket number hash % (arrayLength-1)
Get a reference to the first Entry object in the bucket and by means of equals method iterate over all entries in the given bucket. Eventually we will find the correct Entry.
If a desired element is not found, return null
You can find excellent information at http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html
To Summarize:
HashMap works on the principle of hashing
put(key, value): HashMap stores both key and value object as Map.Entry. Hashmap applies hashcode(key) to get the bucket. if there is collision ,HashMap uses LinkedList to store object.
get(key): HashMap uses Key Object's hashcode to find out bucket location and then call keys.equals() method to identify correct node in LinkedList and return associated value object for that key in Java HashMap.
Here is a rough description of HashMap's mechanism, for Java 8 version, (it might be slightly different from Java 6).
Data structures
Hash table
Hash value is calculated via hash() on key, and it decide which bucket of the hashtable to use for a given key.
Linked list (singly)
When count of elements in a bucket is small, a singly linked list is used.
Red-Black tree
When count of elements in a bucket is large, a red-black tree is used.
Classes (internal)
Map.Entry
Represent a single entity in map, the key/value entity.
HashMap.Node
Linked list version of node.
It could represent:
A hash bucket.
Because it has a hash property.
A node in singly linked list, (thus also head of linkedlist).
HashMap.TreeNode
Tree version of node.
Fields (internal)
Node[] table
The bucket table, (head of the linked lists).
If a bucket don't contains elements, then it's null, thus only take space of a reference.
Set<Map.Entry> entrySet
Set of entities.
int size
Number of entities.
float loadFactor
Indicate how full the hash table is allowed, before resizing.
int threshold
The next size at which to resize.
Formula: threshold = capacity * loadFactor
Methods (internal)
int hash(key)
Calculate hash by key.
How to map hash to bucket?
Use following logic:
static int hashToBucket(int tableSize, int hash) {
return (tableSize - 1) & hash;
}
About capacity
In hash table, capacity means the bucket count, it could be get from table.length.
Also could be calculated via threshold and loadFactor, thus no need to be defined as a class field.
Could get the effective capacity via: capacity()
Operations
Find entity by key.
First find the bucket by hash value, then loop linked list or search sorted tree.
Add entity with key.
First find the bucket according to hash value of key.
Then try find the value:
If found, replace the value.
Otherwise, add a new node at beginning of linked list, or insert into sorted tree.
Resize
When threshold reached, will double hashtable's capacity(table.length), then perform a re-hash on all elements to rebuild the table.
This could be an expensive operation.
Performance
get & put
Time complexity is O(1), because:
Bucket is accessed via array index, thus O(1).
Linked list in each bucket is of small length, thus could view as O(1).
Tree size is also limited, because will extend capacity & re-hash when element count increase, so could view it as O(1), not O(log N).
The hashcode determines which bucket for the hashmap to check. If there is more than one object in the bucket then a linear search is done to find which item in the bucket equals the desired item (using the equals()) method.
In other words, if you have a perfect hashcode then hashmap access is constant, you will never have to iterate through a bucket (technically you would also have to have MAX_INT buckets, the Java implementation may share a few hash codes in the same bucket to cut down on space requirements). If you have the worst hashcode (always returns the same number) then your hashmap access becomes linear since you have to search through every item in the map (they're all in the same bucket) to get what you want.
Most of the time a well written hashcode isn't perfect but is unique enough to give you more or less constant access.
You're mistaken on point three. Two entries can have the same hash code but not be equal. Take a look at the implementation of HashMap.get from the OpenJdk. You can see that it checks that the hashes are equal and the keys are equal. Were point three true, then it would be unnecessary to check that the keys are equal. The hash code is compared before the key because the former is a more efficient comparison.
If you're interested in learning a little more about this, take a look at the Wikipedia article on Open Addressing collision resolution, which I believe is the mechanism that the OpenJdk implementation uses. That mechanism is subtly different than the "bucket" approach one of the other answers mentions.
import java.util.HashMap;
public class Students {
String name;
int age;
Students(String name, int age ){
this.name = name;
this.age=age;
}
#Override
public int hashCode() {
System.out.println("__hash__");
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
System.out.println("__eq__");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Students other = (Students) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
Students S1 = new Students("taj",22);
Students S2 = new Students("taj",21);
System.out.println(S1.hashCode());
System.out.println(S2.hashCode());
HashMap<Students,String > HM = new HashMap<Students,String > ();
HM.put(S1, "tajinder");
HM.put(S2, "tajinder");
System.out.println(HM.size());
}
}
Output:
__ hash __
116232
__ hash __
116201
__ hash __
__ hash __
2
So here we see that if both the objects S1 and S2 have different content, then we are pretty sure that our overridden Hashcode method will generate different Hashcode(116232,11601) for both objects. NOW since there are different hash codes, so it won't even bother to call EQUALS method. Because a different Hashcode GUARANTEES DIFFERENT content in an object.
public static void main(String[] args) {
Students S1 = new Students("taj",21);
Students S2 = new Students("taj",21);
System.out.println(S1.hashCode());
System.out.println(S2.hashCode());
HashMap<Students,String > HM = new HashMap<Students,String > ();
HM.put(S1, "tajinder");
HM.put(S2, "tajinder");
System.out.println(HM.size());
}
}
Now lets change out main method a little bit. Output after this change is
__ hash __
116201
__ hash __
116201
__ hash __
__ hash __
__ eq __
1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally calls Equal method to verify this.
Conclusion
If hashcode is different , equal method will not get called.
if hashcode is same, equal method will get called.
Thanks , hope it helps.
two objects are equal, implies that they have same hashcode, but not vice versa.
2 equal objects ------> they have same hashcode
2 objects have same hashcode ----xxxxx--> they are NOT equal
Java 8 update in HashMap-
you do this operation in your code -
myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");
so, suppose your hashcode returned for both keys "old" and "very-old" is same. Then what will happen.
myHashMap is a HashMap, and suppose that initially you didn't specify its capacity. So default capacity as per java is 16. So now as soon as you initialised hashmap using the new keyword, it created 16 buckets. now when you executed first statement-
myHashmap.put("old","old-value");
then hashcode for "old" is calculated, and because the hashcode could be very large integer too, so, java internally did this - (hash is hashcode here and >>> is right shift)
hash XOR hash >>> 16
so to give as a bigger picture, it will return some index, which would be between 0 to 15. Now your key value pair "old" and "old-value" would be converted to Entry object's key and value instance variable. and then this entry object will be stored in the bucket, or you can say that at a particular index, this entry object would be stored.
FYI- Entry is a class in Map interface- Map.Entry, with these signature/definition
class Entry{
final Key k;
value v;
final int hash;
Entry next;
}
now when you execute next statement -
myHashmap.put("very-old","very-old-value");
and "very-old" gives same hashcode as "old", so this new key value pair is again sent to the same index or the same bucket. But since this bucket is not empty, then the next variable of the Entry object is used to store this new key value pair.
and this will be stored as linked list for every object which have the same hashcode, but a TRIEFY_THRESHOLD is specified with value 6. so after this reaches, linked list is converted to the balanced tree(red-black tree) with first element as the root.
Each Entry object represents key-value pair. Field next refers to other Entry object if a bucket has more than 1 Entry.
Sometimes it might happen that hashCodes for 2 different objects are the same. In this case 2 objects will be saved in one bucket and will be presented as LinkedList. The entry point is more recently added object. This object refers to other object with next field and so one. Last entry refers to null.
When you create HashMap with default constructor
Array is gets created with size 16 and default 0.75 load balance.
(Source)
Hash map works on the principle of hashing
HashMap get(Key k) method calls hashCode method on the key object and applies returned hashValue to its own static hash function to find a bucket location(backing array) where keys and values are stored in form of a nested class called Entry (Map.Entry) . So you have concluded that from the previous line that Both key and value is stored in the bucket as a form of Entry object . So thinking that Only value is stored in the bucket is not correct and will not give a good impression on the interviewer .
Whenever we call get( Key k ) method on the HashMap object . First it checks that whether key is null or not . Note that there can only be one null key in HashMap .
If key is null , then Null keys always map to hash 0, thus index 0.
If key is not null then , it will call hashfunction on the key object , see line 4 in above method i.e. key.hashCode() ,so after key.hashCode() returns hashValue , line 4 looks like
int hash = hash(hashValue)
and now ,it applies returned hashValue into its own hashing function .
We might wonder why we are calculating the hashvalue again using hash(hashValue). Answer is It defends against poor quality hash functions.
Now final hashvalue is used to find the bucket location at which the Entry object is stored . Entry object stores in the bucket like this (hash,key,value,bucketindex)
I will not get into the details of how HashMap works, but will give an example so we can remember how HashMap works by relating it to reality.
We have Key, Value ,HashCode and bucket.
For sometime, we will relate each of them with the following:
Bucket -> A Society
HashCode -> Society's address(unique always)
Value -> A House in the Society
Key -> House address.
Using Map.get(key) :
Stevie wants to get to his friend's(Josse) house who lives in a villa in a VIP society, let it be JavaLovers Society.
Josse's address is his SSN(which is different for everyone).
There's an index maintained in which we find out the Society's name based on SSN.
This index can be considered to be an algorithm to find out the HashCode.
SSN Society's Name
92313(Josse's) -- JavaLovers
13214 -- AngularJSLovers
98080 -- JavaLovers
53808 -- BiologyLovers
This SSN(key) first gives us a HashCode(from the index table) which is nothing but Society's name.
Now, mulitple houses can be in the same society, so the HashCode can be common.
Suppose, the Society is common for two houses, how are we going to identify which house we are going to, yes, by using the (SSN)key which is nothing but the House address
Using Map.put(key,Value)
This finds a suitable society for this Value by finding the HashCode and then the value is stored.
I hope this helps and this is open for modifications.
Bearing in mind the explanations here for the structure of a hashmap, perhaps someone could explain the following paragraph on Baeldung :-
Java has several implementations of the interface Map, each one with its own particularities.
However, none of the existing Java core Map implementations allow a Map to handle multiple values for a single key.
As we can see, if we try to insert two values for the same key, the second value will be stored, while the first one will be dropped.
It will also be returned (by every proper implementation of the put(K key, V value) method):
Map<String, String> map = new HashMap<>();
assertThat(map.put("key1", "value1")).isEqualTo(null);
assertThat(map.put("key1", "value2")).isEqualTo("value1");
assertThat(map.get("key1")).isEqualTo("value2");
It gonna be a long answer , grab a drink and read on …
Hashing is all about storing a key-value pair in memory that can be read and written faster. It stores keys in an array and values in a LinkedList .
Lets Say I want to store 4 key value pairs -
{
“girl” => “ahhan” ,
“misused” => “Manmohan Singh” ,
“horsemints” => “guess what”,
“no” => “way”
}
So to store the keys we need an array of 4 element . Now how do I map one of these 4 keys to 4 array indexes (0,1,2,3)?
So java finds the hashCode of individual keys and map them to a particular array index .
Hashcode Formulae is -
1) reverse the string.
2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .
3) So hashCode() of girl would be –(ascii values of l,r,i,g are 108, 114, 105 and 103) .
e.g. girl = 108 * 31^0 + 114 * 31^1 + 105 * 31^2 + 103 * 31^3 = 3173020
Hash and girl !! I know what you are thinking. Your fascination about that wild duet might made you miss an important thing .
Why java multiply it with 31 ?
It’s because, 31 is an odd prime in the form 2^5 – 1 . And odd prime reduces the chance of Hash Collision
Now how this hash code is mapped to an array index?
answer is , Hash Code % (Array length -1) . So “girl” is mapped to (3173020 % 3) = 1 in our case . which is second element of the array .
and the value “ahhan” is stored in a LinkedList associated with array index 1 .
HashCollision - If you try to find hasHCode of the keys “misused” and “horsemints” using the formulae described above you’ll see both giving us same 1069518484. Whooaa !! lesson learnt -
2 equal objects must have same hashCode but there is no guarantee if
the hashCode matches then the objects are equal . So it should store
both values corresponding to “misused” and “horsemints” to bucket 1
(1069518484 % 3) .
Now the hash map looks like –
Array Index 0 –
Array Index 1 - LinkedIst (“ahhan” , “Manmohan Singh” , “guess what”)
Array Index 2 – LinkedList (“way”)
Array Index 3 –
Now if some body tries to find the value for the key “horsemints” , java quickly will find the hashCode of it , module it and start searching for it’s value in the LinkedList corresponding index 1 . So this way we need not search all the 4 array indexes thus making data access faster.
But , wait , one sec . there are 3 values in that linkedList corresponding Array index 1, how it finds out which one was was the value for key “horsemints” ?
Actually I lied , when I said HashMap just stores values in LinkedList .
It stores both key value pair as map entry. So actually Map looks like this .
Array Index 0 –
Array Index 1 - LinkedIst (<”girl” => “ahhan”> , <” misused” => “Manmohan Singh”> , <”horsemints” => “guess what”>)
Array Index 2 – LinkedList (<”no” => “way”>)
Array Index 3 –
Now you can see While traversing through the linkedList corresponding to ArrayIndex1 it actually compares key of each entry to of that LinkedList to “horsemints” and when it finds one it just returns the value of it .
Hope you had fun while reading it :)
As it is said, a picture is worth 1000 words. I say: some code is better than 1000 words. Here's the source code of HashMap. Get method:
/**
* Implements Map.get and related methods
*
* #param hash hash for key
* #param key the key
* #return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
So it becomes clear that hash is used to find the "bucket" and the first element is always checked in that bucket. If not, then equals of the key is used to find the actual element in the linked list.
Let's see the put() method:
/**
* Implements Map.put and related methods
*
* #param hash hash for key
* #param key the key
* #param value the value to put
* #param onlyIfAbsent if true, don't change existing value
* #param evict if false, the table is in creation mode.
* #return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
It's slightly more complicated, but it becomes clear that the new element is put in the tab at the position calculated based on hash:
i = (n - 1) & hash here i is the index where the new element will be put (or it is the "bucket"). n is the size of the tab array (array of "buckets").
First, it is tried to be put as the first element of in that "bucket". If there is already an element, then append a new node to the list.

don't understand what is going on with hashmap when 2 objects have same hashcode

public class random {
public static class A{
int id= 1;
public int hashCode(){
return id;
}
}
public static void main(String[] args) {
Map<Integer, Integer> map= new HashMap<Integer, Integer>();
A a = new A();
A b = new A();
map.put(a.id, 1);
map.put(b.id, 2);
System.out.println(map.get(a.id));
System.out.println(map.get(b.id));
System.out.println(map.size());
}
}
output is
2
2
1
a and b have the same hashcode(bucket) and inside this bucket is (1 and 2). It says only one node exists which is b with the value of 2. When 2 hashcodes are the same do they override the current value? I read it is not the case.
from tutorial
*"Since hashcode is same, bucket location would be same and collision will occur in HashMap, Since HashMap use LinkedList to store object, this entry (object of Map.Entry comprise key and value ) will be stored in LinkedList.
Read more: http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html#ixzz2sCM7fNED*
does this mean that my map now has 1 bucket with 2 values? how do I grab the value of (1)
You are using the Integer id as a key in the Map, not a or b. So the hashcode() of A is not used at all.
To get the values from a map, use the get method.
If you wanted to use a1 or a2 as the key, you would need to declare the map as such:
Map<A, Integer> map = new HashMap<>();
from the doc of hashmap of java (http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html)
HashMap.put()
Associates the specified value with the specified key in this map. If
the map previously contained a mapping for the key, the old value is
replaced.
So the new value will replace the old value.
Both of them have the same id of "1", so when you add them to the map under the key of their id, they both get added with the key 1. So the second one overrides the first.
You've made a bit of a mess.
A Map maps between keys and values. In your example, you're mapping integers to integers. First you're trying to map 1 to 1, then 1 to 2. A map can only map a key once - if you map the key a second time, it overwrites the first mapping.
Hash collisions happen when two different keys have the same hash value. If you created a Map<A, Integer> and mapped A and B to to some values, you would have had a collision, but would have gotten a map with two elements.

Java - HashMap confusion about collision handling and the get() method

I'm using a HashMap and I haven't been able to get a straight answer on how the get() method works in the case of collisions.
Let's say n > 1 objects get placed in the same key. Are they stored in a LinkedList? Are they overwritten so that only the last object placed in that key exists there anymore? Are they using some other collision method?
If they are placed in a LinkedList, is there a way to retrieve that entire list? If not, is there some other built in map for Java in which I can do this?
For my purposes, separate chaining would be ideal, as if there are collisions, I need to be able to look through the list and get information about all the objects in it. What would be the best way to do this in Java?
Thanks for all your help!
The documentation for Hashmap.put() clearly states, "Associates the specified value with the specified key in this map. If the map previously contained a mapping for the key, the old value is replaced"
If you would like to have a list of objects associated with a key, then store a list as the value.
Note that 'collision' generally refers to the internal working of the HashMap, where two keys have the same hash value, not the use of the same key for two different values.
Are they overwritten so that only the last object placed in that key exists there anymore?
Yes, assuming you're putting multiple values with the same key (according to Object.equals, not Object.hashCode.) That's specified in the Map.put javadoc:
If the map previously contained a mapping for the key, the old value is replaced by the specified value.
If you want to map a key to multiple values, you're probably better off using something like Guava's ListMultimap, ArrayListMultimap in specific, which maps keys to lists of values. (Disclosure: I contribute to Guava.) If you can't tolerate a third-party library, then really you have to have a Map<Key, List<Value>>, though that can get a bit unwieldy.
Let's say n > 1 objects get placed in the same key. Are they stored in a linked list? Are they overwritten so that only the last object placed in that key exists there anymore? Are they using some other collision method?
There could be single instance for the same key so the last one overrides the prior one
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("a", 2);// it overrides 1 and puts 2 there
chaining comes where there turns the same hash for different keys
See
Java papers hash table working
Cite: "Let's say n > 1 objects get placed in the same key. Are they stored in a linked list? Are they overwritten so that only the last object placed in that key exists there anymore? Are they using some other collision method?"
Yes, if the hashmap contained something under this key, it will override it.
You can implement your own class to handle that or more simple use a HashMap> in where K is your Key Object and V the object value.
Have in mind that with last solution when you do a map.get(K) will retrieve a List or the implementation that you choose (i.e: ArrayList) so all the methods of this implementation are available for you and perhaps fulfils your requirements. For example if you used Arraylist you have the size, trimToSize, removeRange, etc.
collision resolution for hashing in java is not based on chaining. To my understanding, JDK uses double hashing which is one of the best way of open addressing. So there's no list going to be associated with a hash slot.
You might put the objects for which the hash function resolves to the same key can be put in list and this list can be updated in the table/map.
package hashing;
import java.util.HashMap;
import java.util.Map;
public class MainAnimal {
/**
* #param args
*/
public static void main(String[] args) {
Animal a1 = new Animal(1);
Animal a2 = new Animal(2);
Map<Animal, String> animalsMap = new HashMap<Animal, String>();
animalsMap.put(a1,"1");
animalsMap.put(a2,"2");
System.out.println(animalsMap.get(a1));
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("a", 2);// it overrides 1 and puts 2 there
System.out.println(map.get("a"));
}
}
class Animal {
private int index = 0;
Animal(int index){
this.index = index;
}
public boolean equals(Object obj){
if(obj instanceof Animal) {
Animal animal = (Animal) obj;
if(animal.getIndex()==this.getIndex())
return true;
else
return false;
}
return false;
}
public int hashCode() {
return 0;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
In the above code, am showing two different things.
case 1 - two different instances resolving to same hashkey
case 2 - two same instances acting as keys for two different entries.
Animal instances, a1 & a2 resolves to same key. But they are not overriden. Hashing mechanism probes through the hash slots and places the entries on different slots.
with the second case, keys resolve to same hash key and also the equals method satisfies. Hence overriding happens.
Now if in the animal class I override the equals method this way -
public boolean equals(Object obj){
// if(obj instanceof Animal) {
// Animal animal = (Animal) obj;
// if(animal.getIndex()==this.getIndex())
// return true;
// else
// return false;
// }
// return false;
return true;
}
Overriding happens. The behavior is like using same instance. Since a1 and a2 are in the same bucket and equals return true as well.

Categories