HashMap with incorrect equals and HashCode implementation - java

According to what I have read,
to use an object as the key to a hashMap, it has to provide a correct
override and implementation of the equals and hashCode
method. 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).
HashMap's internal hash Method defends against poor quality hash
functions.
To test these contracts, I have written a bean class with incorrect but legal implementations of the equals and hashCode method.
The class:
public class HashVO {
private String studentName;
private int age;
private boolean isAdult;
public HashVO(String studentName, int age, boolean isAdult) {
super();
this.studentName = studentName;
this.age = age;
this.isAdult = isAdult;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isAdult() {
return isAdult;
}
public void setAdult(boolean isAdult) {
this.isAdult = isAdult;
}
#Override
public String toString() {
return studentName + " : " + age + " : " + isAdult;
}
#Override
public boolean equals(Object obj) {
return false;
}
#Override
public int hashCode() {
return 31;
}
}
In this case, the hash method of the HashMap,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
should also return same value everytime because the hashcode always returns 31. So if objects of class HashVO are used as key of a hashMap, the get method should not work, as it should go to the same bucket to retrieve the objects and the equals method always returns false so it will not be able to able to find a match for the key object.
But when I am using this method,
public static void main(String[] args) {
HashMap<HashVO, String> voMap = new HashMap<HashVO, String>();
HashVO vo = new HashVO("Item1", 25, true);
HashVO vo1 = new HashVO("Item2", 12, false);
HashVO vo2 = new HashVO("Item3", 1, false);
voMap.put(vo, "Item");
voMap.put(vo1, "Item1");
voMap.put(vo2, "Item2");
System.out.println(voMap.get(vo));
System.out.println(voMap.get(vo1));
System.out.println(voMap.get(vo2));
}
the output is correct, and showing
Item
Item1
Item2
I want to understand why this correct output is coming even as the Equals and HashCode method implementation is incorrect.

HashMap has a little trick where it compares object references before using equals. Since you're using the same object references for adding the elements and for retrieving them, HashMap will return them correctly.
See Java 7 source here (Java 8 did a pretty big revamp of HashMap but it does something similar)
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
// HERE. Uses == with the key
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
Note that this isn't part of the docs, so don't depend on it.

The HashMap works like this:
1) Index of table cell in which (Key,Value) will save calculates as key.hashCode();
2) Keys in HashMap compare by equals() or by reference comparing.
So, in your situation all pairs of (K,V) will store in one cell of HashMap table as LinkedList.
And you can get them from Map because references for keys will equals

Related

Why is equals() not called while adding to HashSet and hashCode matches?

When I run this code why only hashCode() is called not equals method while my hashCode() implementation generate same hashCode for both entries to HashSet?
import java.util.HashSet;
public class Test1 {
public static void main(String[] args) {
Student st=new Student(89);
HashSet st1=new HashSet();
st1.add(st);
st1.add(st);
System.out.println("Ho size="+st1.size());
}
}
class Student{
private int name;
private int ID;
public Student(int iD) {
super();
this.ID = iD;
}
#Override
public int hashCode() {
System.out.println("Hello-hashcode");
return ID;
}
#Override
public boolean equals(Object obj) {
System.out.println("Hello-equals");
if(obj instanceof Student){
if(this.ID==((Student)obj).ID){
return true;
}
else{
return false;
}
}
return false;
}
}
The output for this is:
Hello-hashcode
Hello-hashcode
Ho size=1
The hash set checks reference equality first, and if that passes, it skips the .equals call. This is an optimization and works because the contract of equals specifies that if a == b then a.equals(b).
I attached the source code below, with this check highlighted.
If you instead add two equal elements that are not the same reference, you get the effect you were expecting:
HashSet st1=new HashSet();
st1.add(new Student(89));
st1.add(new Student(89));
System.out.println("Ho size="+st1.size());
results in
$ java Test1
Hello-hashcode
Hello-hashcode
Hello-equals
Ho size=1
Here's the source code from OpenJDK 7, with equality optimization indicated (from HashMap, the underlying implementation of HashSet):
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// v-- HERE
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
A HashSet uses a HashMap as its backing mechanism for the set. Normally, we would expect that hashCode and equals would be called to ensure that there are no duplicates. However, the put method (which calls a private putVal method to do the actual operation) makes an optimization in the source code:
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
If the hash codes match, it first checks to see if the keys are the same before calling equals. You are passing the same Student object, so they are already ==, so the || operator short-circuits, and equals is never called.
If you passed in a different Student object but with the same ID, then == would return false and equals would be called.
Looking through the source code of HashSet it is using HashMap for all of its operations and the add method performs put(element, SOME_CONSTANT_OBJECT). Here is the source code for the put method for JDK 1.6.0_17 :
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
as you can see it performs a == comparison before using the equals method. Since you are adding the same instance of an object twice the == returns true and the equals method is never called.
Equals is always called after the hashCode method in a java hashed
collection while adding and removing elements. The reason being, if
there is an element already at the specified bucket, then JVM checks
whether it is the same element which it is trying to put.
hashcode() and equals() method
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.

LinkedHashSet Not Removing Duplicates

I am trying to create a search algorithm that stores coordinate pairs in a wrapper class called HashSquareSpec. In order to avoid duplicates, and maintain insertion order, I am inserting each HashSquareSpec into a LinkedHashSet. Even though I have overridden the equals() method and hashCode() methods, the LinkedHashSet still accepts two HashSquareSpec objects with
the same coordinate pairs.
public static void main(String [] args)
{
LinkedHashSet<HashSquareSpec> firedShots = new HashLinkedSet<HashSquareSpec>();
HashSquareSpec a = new HashSquareSpec(1,2);
HashSquareSpec b = new HashSquareSpec(2,2);
HashSquareSpec c = new HashSquareSpec(1,2);
HashSquareSpec d = new HashSquareSpec(3,2);
firedShots.add(a);
firedShots.add(b);
firedShots.add(c);
firedShots.add(d);
System.out.println(a.equals((SquareSpec)c));
Iterator l = firedShots.iterator();
while(l.hasNext())
{
System.out.println(l.next().hashCode());
}
}
Output:
true
38444474
38474265
38444474
38504056
HashSquare class
public class HashSquareSpec extends SquareSpec
{
public HashSquareSpec(int sx, int sy)
{
super(sx,sy);
}
public HashSquareSpec(String codeString)
{
super(codeString);
}
#Override
public int hashCode()
{
return this.toString().hashCode();
}
public boolean equals(HashSquareSpec other)
{
if(this.toString().equals(other.toString()))
return true;
else
return false;
}
}
and the super class of HashSquareSpec
public class SquareSpec {
public int x;
public int y;
public SquareSpec(int sx, int sy) {
this.x = sx;
this.y = sy;
}
public SquareSpec(String codeString) {
this.x = Integer.parseInt(codeString.substring(1,2));
this.y = Integer.parseInt(codeString.substring(3,4));
}
public String toString() {
return("(" + x + "," + y + ")");
}
public boolean equals(SquareSpec other) {
return (other.x == this.x &&
other.y == this.y );
}
}
Despite many different hashCode variations and Eclipse equals and hashCode generation, the
firedShots data structure keeps accepting duplicates. What is wrong with my code?
You are on the right track, overriding hashcode and equals, except you are incorrectly overriding the equals method from Object in HashSquareSpec (and SquareSpec). The parameter must be an Object. Because it's not overridden, equals from Object is called, which compares object references to see if they're the same object. They aren't, so the "duplicate" is allowed.
Try:
#Override
public boolean equals(Object other)
{
if(this.toString().equals(other.toString()))
return true;
else
return false;
}
You should also test if other is null and then ensure that other is the same type.
Include the #Override annotation so that the compiler will complain if the method doesn't actually override anything.
It's still accepting because you are not overriding the equals method. You need to override boolean equals(Object). The problem is that you're defining a new method like boolean equals(SquareSpec).
Here is the method that LinkedHashSet#add(T) eventually invokes:
HashMap#put(K, V):
#Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = secondaryHash(key.hashCode());
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
As you can see, it compares using hashCode and equals(Object).

using Natural order with treeset in java

I am getting an unexpected output for this. Please have a look. I am not able to find the problem. What's wrong with my program? Can anybody explain? I am getting the output
Joe Sue Mike Clare Juliet
Joe Mike Clare Juliet
objects in TreeSets and TreeMaps and with Collections.sort() for Lists, using the Comparable Interface.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
class Person implements Comparable<Person> {
private String name;
public Person(String name) {
this.name = name;
}
public String toString() {
return name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.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;
final Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
#Override
public int compareTo(Person person) {
int len1 = name.length();
int len2 = person.name.length();
if(len1 > len2) {
return 1;
}
else if(len1 < len2) {
return -1;
}
else {
return 0;
}
}
}
public class App {
public static void main(String[] args) {
List<Person> list = new ArrayList<Person>();
SortedSet<Person> set = new TreeSet<Person>();
//adding Element
addElements(list);
addElements(set);
//sorting element
Collections.sort(list);
//displaying result
showElements(list);
System.out.println();
showElements(set);
}
//adding element methods
private static void addElements(Collection<Person> col) {
col.add(new Person("Joe"));
col.add(new Person("Sue"));
col.add(new Person("Juliet"));
col.add(new Person("Clare"));
col.add(new Person("Mike"));
}
private static void showElements(Collection<Person> col) {
for(Person element: col) {
System.out.println(element);
}
}
}
You are comparing the persons by the length of their names. And the names "Joe" and "Sue" have the same length. So only one of them can occur in the TreeSet. However, this comparison criterion is not consistent with the implementation of equals!
You should place your Person objects into a list, and sort this list with Collections#sort - preferably, with an own Comparator. Also see https://stackoverflow.com/a/21659849
EDIT: Further explaination:
A Set can contain each element only once. And by the way that you specified your compareTo method, you impled that "Sue" and "Joe" are equal (because their names have equal lengths). So they can not both appear in a Set.
Note: They are not really equal, based on the equals method. But the TreeSet uses the compareTo method, and this compareTo method is currently not consistent with equals. So the Set shows a wrong behavior because of your wrong compareTo method.
EDIT: A possible solution:
If the names have equal lengths, you can compare them alphabetically. This way, the compareTo method becomes consistent with equals: It will return 0 if and only if the names are equal.
#Override
public int compareTo(Person person) {
int len1 = name.length();
int len2 = person.name.length();
if(len1 > len2) {
return 1;
}
else if(len1 < len2) {
return -1;
}
else {
return name.compareTo(person.name);
}
}
A SortedSet uses compareTo for evaluation if an element can be added to the Set. Your input contains two names with same length (your comparison criterion), hence one of Joe or Sue must be filtered out (in your case Sue).
The TreeSet documentation talks about having(not mandatory) compareTo implementaion consistent with equals. In your case 'Joe' and 'Sue' are of equal length and as per your compareTo implementation they are same. One workaround would be to compare the hashcode values in compareTo methods if they are of equal length.

Java removing object from hashmap

public Map<Object> map = new HashMap<Object>();
map.add(new Object ("a", "b"));
public class Object {
private String a;
private String b;
private String c;
public Object(String a, String b) {
this.a = a;
this.a = b;
this.c = "" + a + b + "";
}
public String getA() {
return a;
}
public String getB() {
return b;
}
public String getC() {
return c;
}
}
I have a hashmap, basically Object.getC() should be a + b in a string.
and in some other class, I need to get the c value and if that hashmap collection index has the exact same c value, it will delete the index:
public static void deleteChest() {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getItems().length == 0) {
Object c = new Object(data.get(i).getA(), data.get(i).getB());
map.remove(c);
}
}
}
data hashmap should have the same index number as the map hashmap, so if you're wondering what is it.
Basically, I loop through all data hashmap items (to get the A, B and then match if C is inside the other hashmap (map)).
Now the question time
How can I check if an object is contained (exists) or delete it (not by index, but by key)? How can I do this (Example above).
You should define the equals and hashCode method for your class.
Otherwise you fall back to the default implementation which checks for object identity, i.e. if the two references points to the same memory location instead of pointing to two objects containing the same data.
The first problem you have is that Map use key to retrieve value.
Set<Object> set = new HashSet();
Then when you are using Set or Map, your class must override hashcode and equals methods. If the do not do that the Set will not know how to properly compare them, to perform any operation.
This is why when you create a new Object(a,b,c) it set can not find them as it use the default value of hashcode with is not related to type members.
You should have to change the public Map<Object> map = new HashMap<Object>(); field to public Map<String,Foo> map = new HashMap<String,Foo>(); and you should change the name of your class from Object to Foo as 'mre' suggestion.
Foo Class
public class Foo {
private String a;
private String b;
private String c;
public Foo(String a, String b) {
this.a = a;
this.a = b;
this.c = getKey();
}
public String getA() {
return a;
}
public String getB() {
return b;
}
public String getC() {
return c;
}
public String getKey() {
return "" + a + b + "";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 : a.hashCode());
result = prime * result + ((b == null) ? 0 : b.hashCode());
result = prime * result + ((c == null) ? 0 : c.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;
Foo other = (Foo) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
if (b == null) {
if (other.b != null)
return false;
} else if (!b.equals(other.b))
return false;
if (c == null) {
if (other.c != null)
return false;
} else if (!c.equals(other.c))
return false;
return true;
}
}
and you can add objects in map as follows
map.put(new Foo("a", "b").getKey(),new Foo("a", "b"));
now you deleteChest() method will be like
public static void deleteChest() {
Foo foo = null;
for (int i = 0; i < data.size(); i++) {
foo = map.get(new Foo(data.get(i).getA(), data.get(i).getB()).getKey())
if( foo != null) {
map.remove(foo.getKey());
}
}
}
hope this will solve your problem..
First, you code cannot be compiled for several reasons.
You called your class Object. FYI java.lang.Object is a base class for all classes. Since real class Object belongs to package java.lang you do not have to import it and therefore it is always available. I do not understand how compiler can solve this naming collision.
Interface Map has 2 parameters: K for key and V for value. Therefre line
public Map<Object> map = new HashMap<Object>();
cannot be compiled. Change it to
public Map<Object, Object> map = new HashMap<Object, Object>();
Or, better use real parameters instead of Object.
Map interface has method put that accepts 2 parameters. Therefore the following line cannot be compiled too.
map.add(new Object ("a", "b"));
Bottom line if you want to use map that associates groups of string use code like following:
Map<String, String> map = new HashMap<>();
map.put("a", "b");
To remove value by key use:
map.remove("a");
To retrieve value use String v = map.get("a")
To retrieve all values use either map.keySet() or map.entrySet().
The solution that work for me is:
map.remove("key");
Try this
if(map.containsKey(key)){
map.remove(map.get(key));
}

Sorting and comparing XML from Java

I was given XML and schema files. My goal was to output all data from the XML (without duplicates) and order this list by the date of birth. Currently I got all data printed out (with duplicates) and I don't know what to do next. I've tried different things, but unsuccessfully.
HashSet will depend on the Node.equals() method to determine equality, and you're adding distinct nodes, albeit with the same underlying text. From the doc:
adds the specified element e to this set if this set contains no
element e2 such that (e==null ? e2==null : e.equals(e2))
I would extract the underlying text (String) from the Node, and a HashSet<String> will determine uniqueness correctly.
EDIT
After reading the post again I realised I need to remove dups too so:
You can use a TreeSet to impose unqiueness and sort by DOB - I presume that a person with the same first name, surname and date of birth is the same person.
First I would wrap your Node in a class that implements Comparable and that also does the getting of all those properties you have. The wrapper needs to implement Comparable as the TreeSet uses this method to decide whether elements are different (a.compareTo(b) != 0) and also how to order them.
public static final class NodeWrapper implements Comparable<NodeWrapper> {
private static final SimpleDateFormat DOB_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private final Element element;
private final Date dob;
private final String firstName;
private final String surName;
private final String sex;
public NodeWrapper(final Node node) {
this.element = (Element) node;
try {
this.dob = DOB_FORMAT.parse(initDateOfBirth());
} catch (ParseException ex) {
throw new RuntimeException("Failed to parse dob", ex);
}
this.firstName = initFirstName();
this.surName = initSurnameName();
this.sex = initSex();
}
private String initFirstName() {
return getNodeValue("firstname");
}
private String initSurnameName() {
return getNodeValue("surname");
}
private String initDateOfBirth() {
return getNodeValue("dateofbirth");
}
private String initSex() {
return getNodeValue("sex");
}
private String getNodeValue(final String name) {
return element.getElementsByTagName(name).item(0).getTextContent();
}
public Node getNode() {
return element;
}
Date getDob() {
return dob;
}
public String getFirstName() {
return firstName;
}
public String getSurName() {
return surName;
}
public String getDateOfBirth() {
return DOB_FORMAT.format(dob);
}
public String getSex() {
return sex;
}
public int compareTo(NodeWrapper o) {
int c;
c = getDob().compareTo(o.getDob());
if (c != 0) {
return c;
}
c = getSurName().compareTo(o.getSurName());
if (c != 0) {
return c;
}
return getFirstName().compareTo(o.getFirstName());
}
#Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.dob != null ? this.dob.hashCode() : 0);
hash = 47 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
hash = 47 * hash + (this.surName != null ? this.surName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final NodeWrapper other = (NodeWrapper) obj;
if (this.dob != other.dob && (this.dob == null || !this.dob.equals(other.dob))) {
return false;
}
if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName)) {
return false;
}
if ((this.surName == null) ? (other.surName != null) : !this.surName.equals(other.surName)) {
return false;
}
return true;
}
#Override
public String toString() {
return "FirstName: " + getFirstName() + ". Surname: " + getSurName() + ". DOB: " + getDateOfBirth() + ". Sex: " + getSex() + ".";
}
}
So if the date of birth, surname and firstname are all equal we assume it is the same person - we return 0. It is good practice, if using compareTo in this way to make it consistent with equals so that if a.compareTo(b)==0 then a.equals(b), I have added the required equals and hashCode methods as well.
Now you can use a TreeSet in your code which will automatically sort and guarantee unqiueness:
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File("file.xml"));
final Set<NodeWrapper> inimesteList = new TreeSet<NodeWrapper>();
final NodeList isa = doc.getElementsByTagName("isa");
for (int i = 0; i < isa.getLength(); i++) {
inimesteList.add(new NodeWrapper(isa.item(i)));
}
final NodeList ema = doc.getElementsByTagName("ema");
for (int i = 0; i < ema.getLength(); i++) {
inimesteList.add(new NodeWrapper(ema.item(i)));
}
final NodeList isik = doc.getElementsByTagName("isik");
for (int i = 0; i < isik.getLength(); i++) {
inimesteList.add(new NodeWrapper(isik.item(i)));
}
System.out.println();
System.out.println("Total: " + inimesteList.size());
for (final NodeWrapper nw : inimesteList) {
System.out.println(nw);
}
I have also added a toString method and used that to print the nodes - this makes the code much cleaner.
The Document approach, while seeming simpler than JAXB, is riddled with this sort of tedium. As you already have a schema I would strongly recommend that you make the move to xjc and JAXB unmarshalling - this will make this sort of stuff hundereds of times easier.
Its better to create a Java Bean (POJO) with the single node details. Override equals() and hashcode() in the same. Store all the Node data into the List of Bean. Then use LinkedHashSet to remove duplicates. Implement Comparable or use Comparator and Collections.sort() to sort the same.
Extend or encapsulate Node in another class and override equals() and hashcode() in the same. Store all the Nodes into the List of new class instance. Then use LinkedHashSet to remove duplicates. Implement Comparable or use Comparator and Collections.sort() to sort the same.

Categories