I had an interview today, and I got the following Java code:
public class Question_6 {
public static void main(String[] args){
Map<Integer,String> map1 = new HashMap<Integer,String>();
map1.put(new Integer(1),"001a");
map1.put(new Integer(1),"001b");
map1.put(new Integer(2),"002");
System.out.println(map1.size());
Map<MyInt,String> map2 = new HashMap<MyInt,String>();
map2.put(new MyInt(1),"001a");
map2.put(new MyInt(1),"001b");
map2.put(new MyInt(2),"002");
System.out.println(map2.size());
}
}
public class MyInt {
int i;
public MyInt(int i) {
this.i = i;
}
}
The questions were:
What will be printed to the console?
Suggest a solution to the problem.
I know now that the answer to the first question is :
2
3
But I don't know why? What is the problem with MyInt?
Your problem is that equals() and hashcode() is not implemented on MyInt.
You are expected to have 2 as a result in both cases.
HashMap, as the name implies, groups the keys into buckets based on the keys' hashcode(). But the default hashcode does not match for two instances of MyInt with the same value.
To determine equality, you have to override equals() as well.
One solution:
public class MyInt {
[...]
#Override
public int hashCode() {
return value;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof MyInt) {
return i == ((MyInt)obj).i;
}
return false;
}
}
You need to override the equals() and hashCode() method in your MyInt class , so that HashMap can comprehend new MyInt(1).equals(new MyInt(1)) is true.
The Integer class overrides the equals() method to do value based comparison. Hashmaps cannot contain two keys that are "equal", so the 2nd insertion into map1 will overwrite the first entry. As well, the hashcode() method is overridden.
However, MyInt does not override the equals() or hashcode() method so equality is memory location based. Therefore, map2 sees three distinct keys and makes three distinct entries.
Map<MyInt,String> map2 = new HashMap<MyInt,String>();
MyInt one = new MyInt(1);
MyInt two = new MyInt(2);
map2.put(one,"001a");
map2.put(one,"001b");
map2.put(two,"002");
System.out.println(map2.size());
Produces an output of 2 in this case because one.equals(one) is true in this case.
map1.put(new Integer(1),"001a");
map1.put(new Integer(1),"001b");//same location in map
map1.put(new Integer(2),"002");
in this part you use the Integer class, Integer class don't allow setting the same location, but your Integer class allow.
Change code like this, and you see the problem
public class Question_6 {
public static void main(String[] args){
Map<Integer,String> map1 = new HashMap<Integer,String>();
map1.put(new Integer(1),"001a");
map1.put(new Integer(2),"001b");
map1.put(new Integer(3),"002");
System.out.println(map1.size());
Map<MyInt,String> map2 = new HashMap<MyInt,String>();
map2.put(new MyInt(1),"001a");
map2.put(new MyInt(2),"001b");
map2.put(new MyInt(3),"002");
System.out.println(map2.size());
}
this code will print ;
3
3
So, your Integer class(myInt) is true but missing
The Integer class overrides the equals() method to do value-based comparison.No need to manually include equals() or hashcode() methods. My solution is as follows
import java.util.HashMap;
import java.util.Map;
public class HashMapEqualsHashcode {
public static void main(String[] args) {
MyInt obj = new MyInt(50);
Map<Integer, String> map1 = new HashMap<Integer, String>();
map1.put(new Integer(1),"001a");
map1.put(new Integer(1),"001b");
map1.put(new Integer(2),"002");
System.out.println("map1 size "+map1.size());
Map<MyInt,String> map2 = new HashMap<MyInt,String>();
map2.put(new MyInt(1),"001a");
map2.put(new MyInt(1),"001b");
map2.put(new MyInt(2),"002");
System.out.println("map2 size "+map2.size());
}
}
class MyInt {
int i;
public MyInt(int i) {
this.i = i;
}
}
Screenshot for the solution
https://i.stack.imgur.com/yVkgL.png
You must override the hashCode() and equals methods. 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
Why??
if you inspect in the source code of HashMap.put method. you can see that this method check both hashcode and equality before inserting. So if you don't override these methods it will use the superclass' (object's) methods which will return different values for different objects. So ALthough for the same key two values will be inserted in separate place of Hashmap. So you need to override those two and make sure that for two equal objects you should return same hashcode.
Code
So your MyInt should be something like
public class MyInt {
int i;
public MyInt(int i) {
this.i = i;
}
public int hashCode() {
return i;
}
public boolean equals(Object obj) {
if (obj instanceof MyInt && i == ((MyInt)obj).i) {
return true;
} else
return false;
}
}
The Integer class overrides the equals() method to do value-based comparison. Hashmaps cannot contain two keys that are "equal", so the 2nd insertion into map1 will overwrite the first entry. As well, the hashcode() method is overridden.
However, Myint does not override the equals() or hashcode() method so equality is memory location-based. Therefore, map2 sees three distinct keys and makes three distinct entries.
Related
This question already has answers here:
HashMap way of doing containsKey not behaving as expected
(2 answers)
Closed 3 years ago.
How does containsKey really work? I know that if I do this:
Map<String, Integer> map = new HashMap<>();
map.put("user1", 1);
map.put("user2", 2);
map.put("user3", 3);
System.out.println(map.containsKey("user1")); // true
containsKey returns true
but If I do this:
Map<Person, Integer> table = new HashMap<>();
table.put(new Person("Steve"), 33);
table.put(new Person("Mark"), 29);
System.out.println(table.containsKey(new Person("Steve"))); // false
so why am I getting false even if I have the correct key? How do I check for value of 33 by using its key?
Here you are using String as key
Map<String, Integer> map = new HashMap<>();
map.put("user1", 1);
And String class ia implementing HashCode and Equals method and hence it is working as expected.
While you are using Person class object as key or any custom class, you should make sure that you override Hashcode and equals method.
HashMap implemention uses hashCode to find bucket and the uses equals if there are multiple entries present in bucket.
When working with maps all the objects that need to be stored in the map should implement equals and hashcode
Here is sample Person class that behaves as you would expect:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
return other.getName().equals(this.name);
}
#Override
public int hashCode() {
return this.name.hashCode();
}
}
HashMap uses equals() to compare whether the keys are equal or not
hashCode() is used to calculate the index in which the item should be inserted
Why am i able to keep duplicate contains in Map as key,
i had heart about map is : it cat't contains duplicate keys
import java.util.LinkedHashMap;
import java.util.HashMap;
class LinkedHasMapDemo
{
#SuppressWarnings("unchecked")
public static void main(String[] args)
{
LinkedHashMap l = new LinkedHashMap();
//{116=kumar, 116=kumar, kumar=kumar, 117=Ram charan, 105=Yash}
//HashMap l = new HashMap();
//{116=kumar, 117=Ram charan, 116=kumar, kumar=kumar, 105=Yash}
l.put("116","kumar"); //key is String Object
l.put(116,"kumar"); //key is Integer Object
l.put("kumar","kumar");
l.put(117,"Ram charan");
l.put(105,"Yash");
System.out.println(l);
}
}
but is in this example i am able to keep duplicate keys in the both LinkedHashMap as well as in HashMap
You are right, a Map does not hold duplicate keys (this only applies to keys, values can be equal). If you put a value under an already added key the previous value will be overridden. Therefore consider the following example:
HashMap<String, Integer> map = new HashMap<>();
map.put("key", 1);
System.out.println(map.get("key")); // Outputs 1
System.out.println(map.size()); // Outputs 1
map.put("key", 2);
System.out.println(map.get("key")); // Outputs 2
System.out.println(map.size()); // Still outputs 1
The problem with your counter-example is that you actually don't have duplicates in your map.
You put 116 (an int or Integer after boxing) and "116" (a String). Since both are of different type the map differentiates them, they are different objects. Consider the following example
HashMap<Object, Integer> map = new HashMap<>();
map.put("116", 1);
System.out.println(map.size()); // Outputs 1
map.put(116, 2);
System.out.println(map.size()); // Now outputs 2
System.out.println("116".equals(116)); // Outputs false
In general you should never use raw-types, that is using HashMap without specifying the generic type to use, like HashMap<String, Integer>. If you don't specify anything it will use HashMap<Object, Object>. By that it allows every Object to be put into the map. In many cases you can and want to restrict this to a specific type only.
Try the following:
String s123="123";
Integer i123=123;
System.out.println(s123.hashCode());
System.out.println(i123.hashCode());
System.out.println(i123.equals(s123)); // you can try s123.equals(i123) too
You can even test it online, just copy/type these lines to http://www.javarepl.com/term.html - you will see that the String has a hashcode of 48690, the Integer has 123, and they do not consider equal to each other.
(Of course it works with 116 too just I did not have that number in front of me while typing this answer)
You don't have duplicates. Integer and String objects are not the same type so "116" and 116 are not equals and they have deferent Hash code
Objects that are equal must have the same hash code within a running process
in equals method if the type is not equals for both objects, it will return false, please check Integer equals implantation
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
also for Hash code they will not be equals in your case :
how String hash code is calculated :
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
And for Integer hash code is it the same integer value so in your case it will be 116 for Integer instance, so those will never be the same.
please avoid raw-types, that is using HashMap without specifying the generic type, please read this article what-is-a-raw-type-and-why-shouldnt-we-use-it for more details
I have a class Abc as below
public class Abc {
int[] attributes;
Abc(int[] attributes){
this.attributes = attributes;
}
}
Overriding the Abc hash code as below
#Override
public int hashCode() {
int hashCode = 0;
int multiplier = 1;
for(int i = attributes.length-1 ; i >= 0 ; i++){
hashCode = hashCode+(attributes[i]*multiplier);
multiplier = multiplier*10;
}
return hashCode;
}
I am using above class to create a list of objects and I want to compare whether the two lists are equal i.e. lists having objects with same attributes.
List<Abc> list1 ;
list1.add(new Abc(new int[]{1,2,4}));
list1.add(new Abc(new int[]{5,8,9}));
list1.add(new Abc(new int[]{3,4,2}));
List<Abc> list2;
list2.add(new Abc(new int[]{5,8,9}));
list2.add(new Abc(new int[]{3,4,2}));
list2.add(new Abc(new int[]{1,2,4}));
How can I compare the above two lists with/without iterating over each list . Also is there any better way to override the hashcode , so that two classes having the same attributes(values and order) should be equal.
You have to override the function equals in your class Abc. If you are using an IDE, it can be used to generates something good enough. For example, Eclipse produces the following:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Abc other = (Abc) obj;
if (!Arrays.equals(attributes, other.attributes)) {
return false;
}
return true;
}
With this equals method, you can now check that two instance of Abc are equal.
If you want to compare your two lists list1 and list2, unfortunately you can not simply do
boolean listsAreEqual = list1.equals(list2); // will be false
because that would not only check if the elements in the lists are the same but also if they are in the same order. What you can do is to compare two sets, because in sets, the elements have no order.
boolean setAreEqual = new HashSet<Abc>(list1).equals(new HashSet<Abc>(list2)); // will be true.
Note that in that case, you should keep your implementation of hashcode() in Abc, for the HashSet to function well. As a general rule, a class that implements equals should also implement hashcode.
The problem with a Set (HashSet are Set) is that by design it will not contain several objects which are equal with each other. Objects are guaranteed to be unique in a set. For example, if you add a new new Abc(new int[]{5,8,9}) in the second set, the two sets will still be equal.
If it bothers you then the possible solution is either to compare two lists, but after having sorted them beforehand (for that you have to provide a comparator or implements compareTo), or use Guava's HashMultiset, which is an unordered container that can contain the same objects multiple times.
Override the equals method to compare objects. As the comments mention, you should be overriding the hashcode method as well when overriding equals method.
By this
so that two classes having the same attributes(values and order) should be equal.
i think you mean two objects having same attributes.
you can try something like this
public boolean equals(Object o) {
if(!(Object instanceOf Abc)) {
return false;
}
Abc instance = (Abc)o;
int[] array = instance.attributes;
for(i=0;i<array.length;i++){
if(array[i]!=this.attributes[i]) {
return false;
}
}
}
Edit: As for the hashcode the concept is that when
object1.equals(object2)
is true, then
object1.hashcode()
and
object2.hashcode()
must return the same value. and hashCode() of an object should be same and consistent through the entire lifetime of it. so generating hashcode based on the value of its instance variables is not a good option as a different hashcode may be generated when the instance variable value changes.
If a HashMap's key is a String[] array:
HashMap<String[], String> pathMap;
Can you access the map by using a newly created String[] array, or does it have to be the same String[] object?
pathMap = new HashMap<>(new String[]{"korey", "docs"}, "/home/korey/docs");
String path = pathMap.get(new String[]{"korey", "docs"});
It will have to be the same object. A HashMap compares keys using equals() and two arrays in Java are equal only if they are the same object.
If you want value equality, then write your own container class that wraps a String[] and provides the appropriate semantics for equals() and hashCode(). In this case, it would be best to make the container immutable, as changing the hash code for an object plays havoc with the hash-based container classes.
EDIT
As others have pointed out, List<String> has the semantics you seem to want for a container object. So you could do something like this:
HashMap<List<String>, String> pathMap;
pathMap.put(
// unmodifiable so key cannot change hash code
Collections.unmodifiableList(Arrays.asList("korey", "docs")),
"/home/korey/docs"
);
// later:
String dir = pathMap.get(Arrays.asList("korey", "docs"));
No, but you can use List<String> which will work as you expect!
Arrays in Java use Object's hashCode() and don't override it (the same thing with equals() and toString()). So no, you cannot shouldn't use arrays as a hashmap key.
You cannot use a plain Java Array as a key in a HashMap. (Well you can, but it won't work as expected.)
But you could write a wrapper class that has a reference to the Array and that also overrides hashCode() and equals().
In most cases, where the Strings inside your array are not pathological and do not include commas followed by a space, you can use Arrays.toString() as a unique key. i.e. your Map would be a Map<String, T>. And the get/put for an array myKeys[] would be
T t = myMap.get(Arrays.toString(myKeys));
myMap.put(Arrays.toString(myKeys), myT);
Obviously you could put in some wrapper code if desired.
A nice side effect is that your key is now immutable. Of course, of you change your array myKeys and then try a get(), you won't find it.
Hashing of Strings is highly optimized. So my guess is that this solution, though it feels a bit slow and kludgy, will be both faster and more memory efficient (less object allocations) than #Ted Hopp solution using an immutable List. Just think about whether Arrays.toString() is unique for your keys. If not, or if there is any doubt, (e.g. the String[] comes from user input) use the List.
Like said you need a wrapper class around your array which overrides equality and hashCode.
e.g.
/**
* We can use this instance as HashKey,
* the same anagram string will refer the same value in the map.
*/
class Anagram implements CharSequence {
private final char[] anagram;
public Anagram(String anagram) {
this.anagram = anagram.toCharArray();
Arrays.sort(this.anagram);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Anagram that = (Anagram) o;
return Arrays.equals(this.anagram, that.anagram);
}
#Override
public int hashCode() {
return Arrays.hashCode(this.anagram);
}
#Override
public int length() {
return anagram.length;
}
#Override
public char charAt(int index) {
return anagram[index];
}
#Override
public CharSequence subSequence(int start, int end) {
return new String(anagram).subSequence(start, end);
}
#Override
public String toString() {
return Arrays.toString(anagram);
}
}
Otherwise declare your map as IdentityHashMap, then the user knows we need to use the same instance for your CRUD.
Ted Hopp is right it will have to be same object.
For information see this example:
public static void main(String[] args) {
HashMap<String[], String> pathMap;
pathMap = new HashMap<String[], String>();
String[] data = new String[]{"korey", "docs"};
pathMap.put(data, "/home/korey/docs");
String path = pathMap.get(data);
System.out.println(path);
}
When you run the above code, it will print "docs".
Since Java 9, you can use Arrays::compare method as a comparator for TreeMap that compares the contents of arrays.
Map<String[], String> map = new TreeMap<>(Arrays::compare);
String[] key1 = {"one", "two"};
String[] key2 = {"one", "two"};
String[] key3 = {"one", "two"};
map.put(key1, "value1");
map.put(key2, "value2");
System.out.println(map.size()); // 1
System.out.println(map.get(key1)); // value2
System.out.println(map.get(key2)); // value2
System.out.println(map.get(key3)); // value2
See also: How to make a Set of arrays in Java?
A running example using the Arrays utility and the hash code it provides:
String[] key1 = { "korey", "docs" };
String value1 = "/home/korey/docs";
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(Arrays.hashCode(key1), value1);
System.out.println(map);
{-1122550406=/home/korey/docs}
This approach is useful if your focus is in storing only. Retrieving using the readable (original) key is simple:
String retrievedValue = map.get(Arrays.hashCode(key1));
System.out.println(retrievedValue);
/home/korey/docs
I need a simple scenario to produce a hashing collision in a HashMap. Could someone please provide one.
Is it possible to produce hashing collision if my hashmap keys are immutable?
Regards,
Raju komaturi
You could create your own type and create a bad hash function:
public class BadHash {
private String aString;
public BadHash(String s) {
aString = s;
}
public int hashCode() {
return aString.length();
}
public boolean equals(Object other) {
// boilerplate stuff
BadHash obj = (BadHash) other;
return obj.aString.equals(aString);
}
}
This will make it easy to create a collision.
An example would be:
BadHash a = new BadHash("a", value1);
BadHash b = new BadHash("b", value2);
hashMap.add(a);
hashMap.add(b);
These two entries would collide because a and b hash to the same value even though they are not equal.
Assuming you can change the key class's hash code method.
public int hashCode() {
return 1; // Or any constant value
}
This will make every single key collide.
Can't get much simpler than this:
Map<String, Object> map = new HashMap<String, Object>();
map.put("a", null);
map.put("a", null);
The simplest way is to set the initialCapacity of the HashMap to a low value and start inserting elements.
I suppose you could also design a class such that two objects can return the same hashCode value even though equals would return false.
From what I can see though, there's no way with the default HashMap to get it to tell you if there's a collision.