I have Map<Date, String. I have two Date objects a, b that are equal. I put a string value to the map associated with key a. Then I try to get map value associated with keys a and b but only a returns value that I've put. Is it possible to get my value with b key. I know this is possible when keys are simple strings. Why this doesn't work with other type of objects?
public class Main {
public static void main(String[] args) {
Map<Date, String> map = new HashMap<Date, String>();
Date a = new Date(20131105);
Date b = new Date(20131105);
map.put(a, "sweet");
System.out.println(map.get(a));
System.out.println(map.get(b));
}
static class Date {
private int ymd;
public Date(int ymd) {
this.ymd = ymd;
}
public int getYmd() {
return ymd;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Date) {
return ((Date) obj).ymd == ymd;
}
return false;
}
}
}
The output is:
sweet
null
Since you're using an HashMap for storing your date objects, you have to override the hashCode() method because the key objects are stored in the data structure using their hashCode.
Very basic hashCode() overriden (just for illustration):
#Override
public int hashCode(){
return ymd;
}
Output :
sweet
sweet
A hashmap hashes the elements using their hashCode function. For most types the hash code is computed using the reference of the object and this is the case with Date. While in your case the two dates have same value, they are not the same object. Thus they have different references and so their hash codes differ. When you lookup an element in a HashMap its hashCode is used and so as b's hashCode is different from a's hashCode you can not find an element with key a by using b.
You need to implement hashCode() method in your Date class
Add below code in your Date class
#Override
public int hashCode() {
return ymd;
}
output
sweet
sweet
Because String,Integer and all wrapper class override hasCode and equals both the method.
But In your case equals() method will return true but you have not override hasCode() method so it will generate different hashcode for both a and b .So map.get(b) will return null value.
Related
I'm trying to use HashSet, while using my own class "Inner" as key type, as below:
import java.util.HashSet;
class Inner {
int i;
String s;
public Inner(int i, String s) {
this.i = i;
this.s = s;
}
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(Object o) {
Inner inner = (Inner) o;
return i == inner.i && s.equals(inner.s);
}
}
public class testEquals {
public static void main(String [] args) {
HashSet<Inner> hi = new HashSet<>();
hi.add(new Inner(1,"abc"));
System.out.println(hi.contains(new Inner(1,"abc")));
}
}
It prints "false"
(1) My question is, as long as I try to use "contains" function, I have to construct a new object from "Inner" class to query, but because it's a new object, hashcode() is different. So I always get a "false" for "contains" functions.
(2) If I change the hashCode() to return "true" like equals when values are the same, then in other scenarios, different objects references are considered as "==" just like one unique reference.
(1) and (2) seems to conflict.
How to solve this?
Thanks!
You should override hashCode in a way that two equal objects will have the same hashCode.
For example:
#Override
public int hashCode() {
return Objects.hash(i,s);
}
I'm not sure what's your problem with (2). If two objects are equal according to equals(), the HashSet should treat them as identical objects, even if they are not.
If, one the other hand, you wish the HashSet to treat any Inner instance as unique (regardless of the values of its instance variables), simply don't override hashCode and equals. However, it is rarely useful to use HashSet without overriding these methods.
You have to overide hashCode() correctly without breaking the equals - hashcode contract.
The contract between equals() and hashCode() is:
If two objects are equal, then they must have the same hash code.
If two objects have the same hash code, they may or may not be equal.
hashCode() does not returns true, it returns an int value. So just make sure it returns same int value for 2 different objects whose equals() return true.
Say we have class RegistrationPlate:
public class RegistrationPlate {
private final String regCode;
private final String country;
public RegistrationPlate(String country, String regCode) {
this.regCode = regCode;
this.country = country;
}
public String getRegCode() {
return this.regCode;
}
public String getCountry() {
return this.country;
}
#Override
public String toString() {
return country + " " + regCode;
}
and I have a hashmap with RegistrationPlate as its key, and owner as its value. If I wanted to search this hashmap given a RegistrationPlate object as a parameter how would I do so? This is how I initially approached it:
HashMap<RegistrationPlate, owner> vehicleRegister = new HashMap<RegistrationPlate, owner>();
public String getOwner(RegistrationPlate plate) {
if (this.vehicleRegister.containsKey(plate.getRegCode())) {
return "The owner is: " + this.vehicleRegister.get(plate);
}
return null;
}
I thought the logic here would work because if the vehicleRegister contained the registrationcode string as a key it would return the string below. Is there a more clean-cut way of accessing the object in the hashmap?
You need to override equals() and hashCode() simultaneously of RegistrationPlate.
To use an object of RegistrationPlate as the key to your HashMap, it is advised to override the equals() and hashCode() methods of RegistrationPlate. Without overriding equals() method the following scenario will occur:
RegistrationPlate rp1 = new RegistrationPlate();
RegistrationPlate rp2 = new RegistrationPlate();
rp1.equals(rp2); // returns false
When you override equals() you also need to override hashCode(). Without that you will find inconsistency in the behavior of the HashMap because of the following scenario:
RegistrationPlate rp1 = new RegistrationPlate();
RegistrationPlate rp2 = new RegistrationPlate();
rp1.equals(rp2); // returns true (because you overriden equals() method to be so)
rp1.hashCode() == rp2.hashCode(); // will be evaluated to false
Since the HashMap has RegistrationPlate as keys, you cannot expect to find a String that is the key. This means that you should do
this.vehicleRegister.containsKey(plate)
instead of
this.vehicleRegister.containsKey(plate.getRegCode())
However, you can call get() directly without calling containsKey() first:
public String get(RegistrationPlate plate) {
return "The owner is: " + this.vehicleRegister.get(plate);
}
More importantly, you must override equals() and hashcode() in RegistrationPlate order to get the behavior that you wish. Otherwise, the default implementations will be used which return true only when the key is a reference to the exact same instance as the on stored in the HashMap. This is rarely the case. You typically want to compare the contents of the reference instead.
boolean equals(Object obj) and int hashCode() should be overriden in RegistrationPlate Class.
HashMap uses hashCode() to look for the group where the object is stored; and it uses equals() to search for that object.
This question already has answers here:
How do I compare strings in Java?
(23 answers)
Closed 8 years ago.
I am executing below code after overriding hashcode method of an object (BookMe). Aim is to override hashcode of the object which i will be using as a key in my map (hashmap). But, after executing I see null values. The is no problem with the actual size of map. below is the code. If I don't override hashcode method I get correct output (i mean all three values).
`
class BookMe{
private String isbn ;
static int i = 0;
public BookMe(String isbn)
{
this.isbn = isbn;
}
public String getIsbnValue()
{
return this.isbn;
}
#Override
public boolean equals(Object o)
{
if(o instanceof BookMe && ((BookMe)o).getIsbnValue() == this.getIsbnValue())
{
return true;
}
else{
return false;
}
}
#Override
public int hashCode()
{
return this.isbn.toString().length() + (++i);
}
}
public class HashMapTest {
public static void main(String[] args) {
Map<BookMe, Integer> map = new HashMap<BookMe, Integer>();
BookMe b1 = new BookMe("Graham");
BookMe b2 = new BookMe("Graham");
BookMe b3 = new BookMe("Graham");
map.put(b1, 19);
map.put(b2, 33);
map.put(b3, 22);
System.out.println("----444444--------");
System.out.println(map.size());
Set <BookMe> set = map.keySet();
System.out.println("------*****------");
for(BookMe bk : set)
{
System.out.println("bk : "+ bk);
System.out.println(map.get(bk));
}
}
}
`
Your hashCode violates the contract of Object::hashCode. It should return the same value for the same object.
From the Javadoc :
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
If each call to hashCode for the same instance returns a different value, you can't expect your HashMap to locate your keys.
If you want a hashCode based on the ISBN, just return the hashCode of the ISBN :
#Override
public int hashCode()
{
return this.isbn.hashCode();
}
You should also fix your equals method :
public boolean equals(Object o)
{
if(o instanceof BookMe && ((BookMe)o).getIsbnValue().equals(this.getIsbnValue()))
{
return true;
}
else{
return false;
}
}
In addition to what Eran said your hash code is ever going to return new hash code even for the same object say when you are putting the element into map and when you are iterating over the same, You should always use equals method to compare the two string.
((BookMe)o).getIsbnValue() == this.getIsbnValue()
Change it to:
((BookMe)o).getIsbnValue().equals(this.getIsbnValue())
Your hashcode method is broken and also in equals method you are comparing string by reference not by value . You can correct the equals method like this
#Override
public boolean equals(Object o) {
return o instanceof BookMe && ((BookMe) o).getIsbnValue().equals(this.getIsbnValue());
}
And to correct hashcode , you can use Objects method to generate hashcode
#Override
public int hashCode() {
return Objects.hash(isbn);
}
The general contract for overridden implementations of this method is
that they behave in a way consistent with the same object's equals()
method: that a given object must consistently report the same hash
value (unless it is changed so that the new version is no longer
considered "equal" to the old), and that two objects which equals()
says are equal must report the same hash value. There's no requirement
that hash values be consistent between different Java implementations,
or even between different execution runs of the same program, and
while two unequal objects having different hashes is very desirable,
this is not mandatory (that is, the hash function implemented need not
be a perfect hash).
Main Question
I have a hashmap filled with vertex objects. Based on an integer (i.e. 1), I want to find that vertex object. See the code below:
public class Playground {
public static void main(String[] args) {
Map<Vertex, String> map1 = new HashMap<Vertex, String>();
Map<Integer, String> map2 = new HashMap<Integer, String>();
Vertex v1 = new Vertex(5);
map1.put(v1, "1");
Vertex v2 = new Vertex(5);
String s = map1.get(v2);
System.out.println(s);
Integer int1 = new Integer(1);
map2.put(int1, "2");
Integer int2 = new Integer(1);
String t = map2.get(int2);
System.out.println(t);
}
}
class Vertex{
public int id;
public Vertex(int id){
this.id = id;
}
#Override
public boolean equals(Object obj) {
Vertex v = (Vertex) obj;
return (this.id == v.id);
}
}
Output:
null
2
As you see above, it works with the Integer object but not the user-defined Vertex object. I overrid the equals method as well.
Addition Information
I have a text file. The first column denotes the tail of an edge. The second the head of an edge. Here is an excerpt:
1 1
1 2
1 8
1 4
2 47646
2 47647
...
I pre-load the vertices 1 - n because...well...I cannot check the keyset of my map each time to see if the vertex already exists. Anyway, so then, based on this text file, I need to locate the vertex with id "x" and add an edge.You might be inclined to ask why I don't use Integer objects as keys instead. Numerous examples online used generic V objects and it makes sense --- each node (irl) would have extra information, whether a stop's name or whatever.
You need to override the hashCode method to make the JVM store and retrieve your objects in HashMap properly.
When a put method is called on a hashed collection such as HashMap, the key object hashcode method is called to decide the bucket to store the object. Then its equals method is called to see whether something already exist there or not.
Similary when you do a get on the HashMap, key hashCode method will be called to find the bucket and to retreive the object.
As you have not overridden the hashcode method in your Vertex class so defualt hashcode implemenataion is used. And hence two your Vertex objects having the same id value may be having a different hashcode.
Read this related post to understand how to override the equals and hashcode method:
What issues should be considered when overriding equals and hashCode in Java?
You can find different articles on google for the same topic. A good google result:
http://javarevisited.blogspot.in/2011/02/how-to-write-equals-method-in-java.html
You need to add hashCode() method and fix your equals() in your Vertex class
Try this:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vertex vertex = (Vertex) o;
if (id != vertex.id) return false;
return true;
}
#Override
public int hashCode() {
return id;
}
Maps use hashCode() to determine which object matches the request. equals() is used by Comparable classes. No two objects can be guaranteed to be identical, even if the supplied properties are equal.
The reason it works with Integer objects is because the Integer class is simply a wrapper around the primitive int type, and every other property/method of the Integer class is identical to another Integer object.
As per the below link
Hashcode and equals
So it is assumed that if 2 objects are equal (that is, equals() returns true), then their hashCodes() must return the same value.
But consider the below example
public class Test {
public static void main(String args[]) {
Demo d1 = new Demo(123);
Demo d2 = new Demo(123);
System.out.println("d1.hashCode()-->" + d1.hashCode());
System.out.println("d2.hashCode()-->" + d2.hashCode());
System.out.println(d1.equals(d2));
}
}
class Demo {
private int name;
Demo(int name) {
this.name = name;
}
#Override
public int hashCode() {
Double d = Math.random() * 1000;
return d.intValue();
}
#Override
public boolean equals(Object o) {
if ((o instanceof Demo) && (((Demo) o).getName() == this.getName())) {
return true;
} else {
return false;
}
}
public int getName() {
return name;
}
}
the output of the above program is
d1.hashCode()-->85
d2.hashCode()-->692
true
Here the question is even though the hash code is different the equals method return true.
So does it mean for equality of object we do not need same hash code
Some implementations (for instance HashMap) depend on the fact that when two instances are equal their hashcode should be the same. However, purely using equals will not check hashcodes for those instances.
In the case of a hashmap, elemets are first divided into different hash buckets based on the hashcodes of objects (for performance reasons). Inside a bucket, equals is used to check object equality. If two equal instances don't have the same hashCode they will end up in different buckets and that might result in unexpected behaviour due to the contract violation.
It is bad implementation of hashCode() , You should follow that contract.
equals has nothing to do with hashcode independently.
But when you use hashing data structure you should must follow this thing.
You should never implement hashCode using random numbers! It should always be computed using the same fields as equals uses. Otherwise, your objects will be unusable as keys in HashMap, HashSet, etc.
Actually the idea is that when you implemente the equals method you should implement the hashCode method, and if a.equals(b) then a.hashCode() == b.hashCode(). Although there isn't anything in the Java compiler or Runtime that enforces this.
In Hashcode it is always required that if two objects are equal then their hashCode always have to be the same other wise the hash collections(HashMap, HashSet etc. wont work correctly)
Well in your case one of the implementations of can be -
public int hashCode() {
return this.getName();
}
OR if you want to mix it up a bit -
public int hashCode() {
return String.valueOf(this.getName()).hashCode();
}
First imp thing you should not use random number to implement the hashcode() method.
#Override
public int hashCode() {
return name.hashcode();
}
#Override
public boolean equals(Object o) {
if(o !=null && o instanceOf Demo)
{
Demo d=(Demo) o;
if(this.d.name==d.name)
{
return true;
}
else
{
false;
}
else
{
return false;
}
}