When does HashSet 'add' method calls equals? [duplicate] - java

This question already has answers here:
What issues should be considered when overriding equals and hashCode in Java?
(11 answers)
Closed 4 years ago.
I did this test in a HashSet comparision and equals is not being called
I would like to consider equals when farAway=false
(A function to check two point distances)
Full compilable code, you could test it, and tells why equals is not being called in this example.
public class TestClass{
static class Posicion
{
private int x;
private int y;
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Posicion other = (Posicion) obj;
if ( farAway(this.x, other.x, this.y, other.y,5)){
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
return hash;
}
Posicion(int x0, int y0) {
x=x0;
y=y0;
}
private boolean farAway(int x, int x0, int y, int y0, int i) {
return false;
}
}
public static void main(String[] args) {
HashSet<Posicion> test=new HashSet<>();
System.out.println("result:"+test.add(new Posicion(1,1)));
System.out.println("result:"+test.add(new Posicion(1,2)));
}
}
EDIT
-Is there a way to force HashSet add to call equals?

If the hash codes differ, there is no need to call equals() since it is guaranteed to return false.
This follows from the general contract on equals() and hashCode():
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.
Right now your class is breaking that contract. You need to fix that.

If you want equals() to be called always, just always return, say, 0 in hashCode(). This way all items have the same hash code and are compared purely with equals().
public int hashCode() {
return 0;
}

It sounds like HashSet isn't right for you.
It sounds like you want a custom way of comparing two positions. Rather than saying "are two positions exactly equal?".
Instead, you should look at using TreeSet, with a Comparator. This way, you can write a "IsWithinRangeComparator" and do your range checking there.

As suggested above,when objects are equal, their hashcode should also be the same.
You could make a simple fix to your hashcode computation like below.
public int hashCode() {
int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
boolean faraway=farAway(this.x, other.x, this.y, other.y,5);
hash=59*hash+(faraway?1:0); //include faraway also as part of hashcode computation
return hash;
}

Related

Java equals() for Objects (Super&Subclasses)

Hi i'm just beginner learning about abstract classes & interfaces.
Everything we build our prof is testing by creating clones and comparing objects.
I've learned overriding the equals() method the detailed way…
#Override
public boolean equals(Object obj){
if (this == obj){
return true;
}
else if (obj == null){
return false;
}
...
else {
Obj x = (*Superclass*)obj;
…
}
I was now wondering if I could replace this long way by a short Version where I change the toString method, do a hashCode of the toString method and compare the hashCodes of my Objects (original & clone).
Would this be ok to do or is there any reason i shouldn't do it?
Would I be able to inherit the toString, hashCode and equals method and just adjust the clone() in subclasses if we assume that the subclasses use the same variables?
My idea was following
public abstract *Superclass*{
public String name; //would be private in org. code
public int hp; //would be private in org. code
public Superclass(){
}
#Override
public Superclass clone(){
return this; //(not sure if this is ok to use)
}
#Override
public String toString(){
Class temp = getClass();
return temp.getName() + this.name + " " + this.hp;
}
#Override
public int hashCode(){
int hcModify = 10;
int hcCurrent = this.toString().hashCode();
return hcModify * hcCurrent;
}
#Override
public boolean equals(Object obj){
return this.hashCode() == obj.hashCode())
}
}
So the first thing to note is that your equals method will throw an error if obj is null - you can't use any . operators on null.
Your clone method is dangerous if there's mutability in play - mutability means "values can be changed". Because it just returns a reference, changes will be reflected in both the original and "cloned" values (because they're the same.) This is not what most developers would expect. (I suggest looking up deep vs. shallow clones, which is related.)
x = new Thing()
y = thing.clone()
x.changeInSomeWay()
//is y now also changed?
The method of using hash codes for equality is not necessarily good or bad - it depends on the relation of the object to its hash and toString functions, and if there are colisions. Some objects will have hash or toString colisions, where different objects will have the same hash or string representations - particularly large or complex objects, where those representations don't include all of the data that you'd want to be reflected in an equality check.
Yours is actually an example of this. You're using an int hashcode, which only has 2^32 (or whatever) possible values, while Strings have, in principle, infinite possible values; by the pigeonhole principal, there must therefor be multiple objects with different names but the same hashcode.
In general, it's not a safe practice, and can lead to weird, difficult to diagnose errors.
I'm not sure why you're multiplying by 10?

How to compare objects in STL data structure

(this question pertains to java)
I tried looking around for this.. gotten some partial answers but nothing that really could help me.
If I have a class pair with 2 variables inside of it, and insert it into a HashSet or TreeSet, how would I access those elements after I insert it.
Ex:
HashSet<Integer>hs=new HashSet<>();
hs.add(new pair(1,2));
hs.get(new pair(1,2));
So here it would throw a null pointer exception, because it is getting a seperate address. How do I modify the data structure to have some sort of comparator that we can compare the actualy elements inside such as : a==other.a && b==other.b
All you need is to override equals and hashCode methods for your pair class. For example:
class Pair {
int a;
int b;
Pair(int a, int b) {
this.a = a;
this.b = b;
}
#Override
public boolean equals(other: Object) {
if (other == null) { return false; }
if (other instanceof Pair) {
Pair otherPair = (Pair)other;
return a == otherPair.a && b == otherPair.b;
}
return false;
}
#Override
public int hashCode() {
return a * 31 + b;
}
}
To learn more, read about equals and hashCode methods in java.lang.Object, and read HashSet documentation.

Comparing objects in HashSet

I'm making a 2D game that has a stars in it. I decided to create constructor in class named Star that gives random coordinates.
public Star(){
super(0,0);
x = randomX.nextInt(maxX - minX + 1);
y = randomY.nextInt(maxX - minY + 1);
}
Then, in other class I put them in HashSet
Set<Star> star = new HashSet<>();
public Set<Star> generateStars(){
while (star.size() < numberOfStars){
star.add(new Star());
}
return star;
}
Of course, I have render and tick methods but I think it's not worth to paste them. My lecturer told me that there can be same stars and to prevent that I should use identity function using hashcodes. Can someone help me figure that out ? I imagine that this function should check if the hashcodes are the same and if it's the case it should return only one value that way we will add 1 object instead of 2 into the HashSet. Am I right ?
Overriding the hashCode() method alone in your Star class will not work you will have to override the equals() method.
See the following code where we don't override the equals() method:
class Star {
int x, y;
public Star(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
return Objects.hash(x, y);
}
}
public class Main {
public static void main(String[] args) {
Star s1 = new Star(0, 0);
Star s3 = new Star(0, 0);
Star s2 = new Star(31, -31*31);
Set<Star> set = new HashSet<>();
set.add(s1);
set.add(s2);
System.out.println(set.size());
}
}
This will print 3 (instead of 2 what you might expect).
The reason for this is that the add method of java.util.Set compares 2 objects based on equals() method and not based on hashCode() method.
In the above code for the Star class, if you add the equals() method the output will be 2 now. For your reference the you can override the equals() method as follows:
#Override
public boolean equals(Object startObject) {
if (this == startObject) return true;
if (startObject == null || getClass() != startObject.getClass()) return false;
Star star = (Star) startObject;
return x == star.x &&
y == star.y;
}
So why do you need to add hashCode()?
As you are using HashSet the add method behind the scene will call the equals() method and will also call hashCode() to decide the bucket in which the new object should be put. To maintain the contract of hashCode() and equals() Both should be overridden.
When ever you override equals(), it is recommended to override hashCode() also. (Vice-versa is also true). See this link for details.
Contract for hashCode() and equals(): If for two objects say o1 and o2, o1.equals(o2) is true then hash of o1 and o2 should be same.
Make sure that you understand this properly, from the above statement it is not implied that if the hash of 2 objects are same, then o1.equals(o2) should return true. It can be possible that for 2 objects, their hash is same but the o1.equals(o2) returns false
See here, what Object's hashCode() method guarantees.
See this link to get more detailed information about this topic.
In java, when adding an object to a HashSet, the add method uses the 'equals' method, which is part of the Object class (however you can override it) in order to determine if the set already contains the object you are trying to add, see : https://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html
However, if you are overriding the equals method then you should also override the hashCode method, this is very well explained in the following post : Why do I need to override the equals and hashCode methods in Java?
If you are going to follow this advice, I would also advise using ApacheCommonsLang EqualsBuilder and HashCodeBuilder if your lecturer allows it as they provide a solid implementation based on the rules laid out in the book 'Effective Java' by Joshua Bloch
To override the equals method in your Star class, you want to think about the criteria that make two star objects equal. From your example this might be that both of them have the same x and y co-ordinates.
you can override hashCode() method from Object. So, in your Star class, add:
#Override
public int hashCode() {
in hash = .... //make your hash code about the coordinates of your star.
return hash;
}
so, when you place a new star with the same co-ordiante in the hash map, it will overwrite the previous start with the same co-ordiantes if already present in the map.
You have to implement hashCode and equals.
the code should be like this:
public static class Star {
int x, y;
public Star(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
return x * 1000 + y;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Star) {
Star s = (Star) obj;
return this.x == s.x && this.y == s.y;
}
return false;
}
}
public static void main(String args[]) throws Exception {
HashSet<Star> set = new HashSet<Star>();
set.add(new Star(1, 1));
set.add(new Star(1, 1));
System.out.println(set.size());
}
note: choose the suitable hashcode function for you. I assumed here that y should always be less than 1000.
If you are using eclipse as your editor just right-click on the editor pane, go-to source then generate hashcode() and equals() pick the parameters you would like to consider. You will get an autogenerated function.

Hashtable get() operation not working as per documentation

According to the official documentation for the Java Hashtable class (https://docs.oracle.com/javase/7/docs/api/java/util/Hashtable.html), the get() opperation will return one of it's recorded values, if said value has a key that returns true when the parameter is fed into that key's equals() opperation.
So, in theory, the following code should return "Hello!" for both of the Hashtable's get() queries:
public static class Coordinates implements Serializable {
private int ex;
private int why;
public Coordinates(int xCo, int yCo) {
ex = xCo;
why = yCo;
}
public final int x() {
return ex;
}
public final int y() {
return why;
}
public boolean equals(Object o) {
if(o == null) {
return false;
} else if(o instanceof Coordinates) {
Coordinates c = (Coordinates) o;
return this.x() == c.x() && this.y() == c.y();
} else {
return false;
}
}
}
Hashtable<Coordinates, String> testTable = new Hashtable<Coordinates, String>();
Coordinates testKey = new Coordinates(3, 1);
testTable.put(testKey, "Hello!");
testTable.get(testKey); //This will return the "Hello" String as expected.
testTable.get(new Coordinates(3, 1)); //This will only return a null value.
However, get() doesn't work as it's supposed to. It seems to only work if you litterally feed it the exact same object as whatever was the original key.
Is there any way to correct this and get the Hashtable to function the way it's described in the documentation? Do I need to make any adjustments to the custom equals() opperation in the Coordinates class?
To be able to store and retrieve objects from hash-based collections you should implement/oeverride the equals() as well as hashCode() methods of the Object class. In your case, you have overridden the equals() and left the hashCode() method to its default implementation inherited from the Object.
Here is the general contract of the hashCode() method you must consider while implementing it:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals 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 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.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
And here is an example implementation that is generated from my IDE (as alread mentioned by #Peter in the comment area) which you can modify to suit your requirements:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ex;
result = prime * result + why;
return result;
}

NullPointerException in LinkedHashMap

I'm creating a game. When I'm reading a LinkedHashMap, it gives me an NPE.
I fill LinkedHashMap hm like this:
for (String s : line.split("")) {
if (s.contains("*")) {
hm.put(new Coordinates(xa-32, ya), "gray");
} else if (s.contains("#")) {
hm.put(new Coordinates(xa-32, ya), "black");
}
// other code...
}
Later I try to get color from the HashMap like this, and get an NPE:
if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A)
&& isPainted && hm.get(new Coordinates(x - 32, y)).equalsIgnoreCase("gray")) {
x -= 32;
}
Full code here:
GamePanel.java
Coordinates.java
On this line hm.get(new Coordinates(x, y - 32)).equalsIgnoreCase("gray")), it is not possible that hm contains newly created Coordinates.
When you create new object, for example Coordinates c = new Coordinates(x, y - 32);, in memory is created that object and the variable c holds reference to that memory, not object itself.
Because of it, look at this code :
Coordinates c1 = new Coordinates(x, y - 32); //c1 holds reference to memory, something like "a8wgge8h"
Coordinates c2 = new Coordinates(x, y - 32); //c2 holds also reference to memory, someting like "a8w12238h"
if (c1 != c2){
System.out.println("Yes, it is true, c1 is not c2, there are two objects with same properties, but they are not same, like human twins - they look same, but two people actually exists");
}
Therefore you cant find anything in hm.get(new Coordinates(x, y - 32)), because it does not look for coordinate which has same x,y, it looks for coordinate with same reference to the memory. And it cannot exists, because you just create new object, java associated it new memory address, something like abnbn147 and then your list/set looks for object with address abnbn147, which cannot be stored there, because you just have just created it.
This hm.get(new Coordinates(x, y - 32)) always return null. If you call method on null, it ends with NullPointerException, which happens with calling method equalsIgnoreCase on null object I was talking about.
To make it work, in class Coordinates implement equals and hashCode methods.
Example:
#Override
public int hashCode()
{
return 10000*x+y;
}
#Override
public boolean equals(Object obj)
{
if(obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Coordinates other = (Coordinates)obj;
return x == other.getX() && y == other.getY();
}
Longer explanation:
LinkedHashMap uses equals method to compare your key with keys already in map. Default equals method compares object references, so if you have
Coordinates a = new Coordinates(1, 1);
Coordinates b = new Coordinates(1, 1);
a.equals(b) returns false. If you not override equals method, hashmap will not find item with your key.
hashCode() must be implemented if you override equals() method.
Whenever a.equals(b), then a.hashCode() must be same as b.hashCode().
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()
This is because you are comparing objects. TO get your desired value for a given key LinkedHashMap uses hashcode and toString methods. However for given 2 objects which refers to 2 different instances of the class can't be same. Root cause of your problem is at hm.put(new Coordinates(xa-32, ya), "gray"); and hm.get(new Coordinates(x - 32, y)) because both objects are different.
However in order to solve this problem you can override hashCode and toString methods in Coordinates class in a such way that gives you same hashcode every time for given cordinates. Hope this will help and provide you a right direction.
For Example.
public class Cordinates {
int x, y;
public Cordinates(int x, int y) {
super();
this.x = x;
this.y = y;
}
public Cordinates() {
super();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Cordinates other = (Cordinates) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
#Override
public String toString() {
return "Cordinates [x=" + x + ", y=" + y + "]";
}
}
In the line hm.get(new Coordinates(x - 32, y)).equalsIgnoreCase("gray") you have no guarantee that the hashmap contains anything that corresponds to the key new Coordinates(x - 32, y) so this is very likely to return null (particularly dependent on how Coordinates implements equals()). This will lead to the comparison null.equalsIgnoreCase("gray"). Hence the null pointer exception. You need to remove the hm.get() statement from the if statement and include a null check.
Whenever I encounter a NullPointerException that is weird, it is %90 because of uninitialized List type of objects.
Try writing
LinkedHashMap<Coordinates, String> hm = new LinkedHashMap<>();
before you add some values to your LinkedHashMap
hm.get(new Coordinates(x, y - 32)) returns null in your case. and hence
hm.get(new Coordinates(x, y - 32)).equals means calling a method on null throws NPE.
why hm.get(new Coordinates(x, y - 32)) returns null in your case ?
when you add key to the hashmap, it creates a hashcode for that and stores it in its cache and while retrieving it refers to the corresponding hashcode available in the cache.
so while adding new coordinates , cordinate object hashcode will be added to hashmap.
in your case cordinate class has not overriden hashcode and equlas method properly.
hence when you create new coordinate object its hashcode will be different for each object even though they are identical.
so even if you create 2 identical object , your hashcode for 2 identical cordinate object will be different because no hashcode overriden and hence Object class hashcode is taken.
How to Avoid this
Override hashcode method inside your coordinate class considering all the attributes for hashcode
and use prime number for calculation
public int hashCode()
{
return 31*x+y;
}
and override equals method as below
public boolean equals(Object obj)
{
if(obj == null)
return false;
if (!(obj instanceof this))
return false;
Coordinates cordinate= (Coordinates)obj;
return x == cordinate.getX() && y == cordinate.getY();
}
So now when you create 2 identical object their hashcode remains the same and while entering those object as key to map, it identifies them as identical.
and if you retrieve value from map by passing key as a new object(which is identical with attributes to the already inserted key) then it gets the value which is not null.
Final Point
hm.put(new Coordinates(x, y - 32),"value");
here new coordinate object has been created , its hashcode is cached by hashmap which will be used later for comparing while retriveing.
hm.get(new Coordinates(x, y - 32));
this will also creates a new coordinate object and its hashcode will be same if we have overriden the hashcode and hence it gets the value and retunns "value" for us.
if not overriden then its new hashcode value will be different and while retriving hashmap searches in its cache but not available and hence returns null.
so make sure hashcode and equals method are properly overriden on the cordinate class.

Categories