Overriding equals method doesn't work - java

I've been browsing a lot of similar questions in here and on other sites. Still I can't seem to get my head wrapped around this problem.
I have a class:
public class Event {
public String Item;
public String Title;
public String Desc;
#Override
public boolean equals(Object o) {
return true;
}
}
I'm trying to use this class in an ArrayList<Event> events but I can't find a way to get events.contains("item") to work. I have tried debuging and I've found that it doesn't even enter the overridden method.
What am I doing wrong?

That's because you're breaking symmetry as specified in the contract of equals(): if any event equals to "item" (which is a String), "item" should also be equal to any event.
Actually, what Java does is to call indexOf("item") on your list, and check if it is positive.
Now, indexOf() works like this in an ArrayList for instance (see complete source code here):
for (int i = 0; i < size; i++)
if ("item".equals(elementData[i]))
return i;
So basically it is the String's equals() method which is called here, not your one which is returning false of course.
Solve this issue by simply specifying an Event parameter to the function, like:
events.contains( new Event("item", "title", "desc") )
Note that you'll have to create a proper constructor for your class ot initialize the members.

You should also override public int hashCode(). The two methods are closely related.
Read more about this: http://www.javapractices.com/topic/TopicAction.do?Id=17

When you override equals() method, you also have to override the hashcode() method because they go hand in hand. If two object are equal, then they have to have the same hashcode. Hashmaps use this to evaluate the save locations. If two objects are not equal, they may or may not have the same hashcode.

In this case, you need only override equals method, not the hashCode method.
The hashCode and equals method should both be overrided when you want to use the object of your class as key in HashMap. HashMap uses a structure of array + linkedList. When adding a key-value pair, it first do some calculation based on key's hashCode to get the index in array; then go throught the linkedList at that index position to find if the key-value pair is already there. If yes, it will overwrite the record with new value; otherwise add the key-value pair to the end of that linkedList. When locating a key, the process is smiliar. So if the hashCode method is not overrided, you will fail the first round search in the array. That's the reason why you need override both methods. It's not like somewhere says there's contract between these two methods or they have close relation.

Related

Should hashCode() only use the subset of immutable fields of those used in equals()?

Situation
I needed to overwrite equals() and as it is recommended I also overwrote the hashCode() method using the same fields. Then, when I was looking at a set, that contained only the one object I got the frustrating result of
set.contains(object)
=> false
while
set.stream().findFirst().get().equals(object)
=> true
I understand now, that this is due to changes that were made to object after it was added to set which again changed its hashCode. contains then looks at the wrong key and can't find the object.
My requirements for the implementation are
mutable fields are needed to correctly implement equals()
use these objects safely in hash-based Collections or Maps such ash HashSet even if they are prone to changes.
which conflicts with the convention that
equals() and hashCode() should use the same fields in order to avoid surprises (as argued here: https://stackoverflow.com/a/22827702).
Question
Are there any dangers to using only a subset of fields which are used in equals() to calculate hashCode() instead of using all?
More specifically this would mean: equals() uses a number of fields of the object whereas hashCode() only uses those fields that are used in equals() and that are immutable.
I think this should be okay, because
the contract is fullfilled: equal objects will produce the same hashCode, while the same hashCode does not necesairly mean that the objects are the same.
The hashCode of an object stays the same, even if an object is exposed to changes and therefore will be found in a HashSet before and after those changes.
Related posts that helped me understand my problem but not how to solve it: What issues should be considered when overriding equals and hashCode in Java? and Different fields for equals and hashcode
It's ok for hashCode() to use a subset of the fields that equals() uses, although it may possibly give you a slight performance drop.
Your problem seems to be caused by modifying the object, while still inside the set, in a way that alters the functioning of hashCode() and/or equals(). Whenever you add an object to a HashSet (or as the key in a HashMap), you must not subsequently modify any fields of that object that are used by equals() and/or hashCode(). Ideally, all fields used by equals() should be final. If they can't be, you must treat them as though they are final whilst the object is in the set.
The same goes for TreeSet/TreeMap, too, but applies to fields used by compareTo().
If you really need to modify the fields that are used by equals() (or by compareTo() in the case of a TreeSet/TreeMap), you must:
First, remove that object from the set;
Then modify the object;
And finally add it back to the set.
The contract would indeed be fulfilled. The contract imposes that .equal() objects have ALWAYS the same .hashCode(). The opposite doesn't have to be true and I wonder with the obsession of some people and IDEs to apply exactly that practice. If this was possible for all possible combinations, then you would discover the perfect hash function.
BTW, IntelliJ offers a nice wizard when generating hashCode and equals by treating those two methods separately and allowing to differentiate your selection. Obviously, the opposite, aka offering more fields in the hashCode() and less fields in the equals() would violate the contract.
For HashSet and similar collections/maps, it's a valid solution to have hashCode() use only a subset of the fields from the equals() method. Of course, you have to think about how useful the hash code is to reduce collisions in the map.
But be aware that the problem comes back if you want to use ordered collections like TreeSet. Then you need a comparator that never gives collisions (returns zero) for "different" objects, meaning that the set can only contain one of the colliding elements. Your equals() description implies that multiple objects will exist that differ only in the mutable fields, and then you lose:
Including the mutable fields in the compareTo() method can change the comparison sign, so that the object needs to move to a different branch in the tree.
Excluding the mutable fields in the compareTo() method limits you to have maximum one colliding element in the TreeSet.
So I'd strongly recommend to think about your object class'es concept of equality and mutability again.
That's perfectly valid to me. Suppose you have a Person:
final int name; // used in hashcode
int income; // name + income used in equals
name decides where the entry will go (think HashMap) or which bucket will be chosen.
You put a Person as a Key inside HashMap : according to hashcode it goes to some bucket, second for example. You upgrade the income and search for that Person in the map. According to hashcode it must be in the second bucket, but according to equals it's not there:
static class Person {
private final String name;
private int income;
public Person(String name) {
super();
this.name = name;
}
public int getIncome() {
return income;
}
public void setIncome(int income) {
this.income = income;
}
public String getName() {
return name;
}
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public boolean equals(Object other) {
Person right = (Person) other;
return getIncome() == right.getIncome() && getName().equals(right.getName());
}
}
And a test:
HashSet<Person> set = new HashSet<>();
Person bob = new Person("bob");
bob.setIncome(100);
set.add(bob);
Person sameBob = new Person("bob");
sameBob.setIncome(200);
System.out.println(set.contains(sameBob)); // false
What you are missing I think is the fact that hashcode decides a bucket where an entry goes (there could be many entries in that bucket) and that's the first step, but equals decides if that is well, an equal entry.
The example that you provide is perfectly legal; but the one you linked is the other way around - it uses more fields in hashcode making it thus incorrect.
If you understand these details that first hashcode is used to understand where and Entry might reside and only later all of them (from the subset or bucket) are tried to be found via equal - your example would make sense.

HashMap.get() doesn't return the proper value, thanks to "hashCode()"

I'm currently working on a TD game with a map editor. Now obviously, you can save and load these maps (or should be able to, at least).
Problem is: at some point I'm calling .get() on a HashMap. Unfortunately, the keys that should be the same (logic-wise) are not the same object (in terms of reference), and, according to my previous google research, overriding their .equals method isn't sufficient, since they still return different hashes with .hashCode() (I verified that, they DO return different hashes, while .equals does return true).
(On a side note, that's rather confusing, since the javadoc of HashMap.get(key) only states that they have to be equal)
More specifically, the HashMap contains instances of a class Path of mine as keys, and should return the corresponding list of enemies (= value).
short version of Path (without getters etc.):
public class Path
{
private List<Tile> tiles = new ArrayList<>();
#Override
public boolean equals(Object obj) {
//code comparing the two paths
}
#Override
public int hashCode() {
//what I still need to implement. ATM, it returns super.hashCode()
}
}
public class Tile
{
private int x;
private int y;
//constructor
//overrides equals
//getters & some convenience methods
}
Now if two Paths are equal, I'd like them to return the same hash code, so that the HashMap returns the correct list of enemies. (I'll make sure not two identical paths can be added).
Now my question:
Do you suggest
using some external library to generate a hash
that I write my own implementation of calculating a hash, or
something else
?
Note that I'd prefer to avoid changing the HashMap to some other type of map, if that would even help solve the problem.
You definitely do need to implement your hashCode consistent with equals. IDEs often do decent job generating hashCode and equals. Also consider Objects.equals(...) and Objects.hash(...).
One warning about using Path as keys in the HashMap. You will have to make the class immutable to make it work reliably. Or at least make sure that hashCode of the key does not change. Otherwise you may not able to get you data back even with the same or equal key.
The List has a useful method which conveniently is also named list.hashCode(). This will compute the hashCode of all the elements inside the list. So you also have to implement the hashCode for Tile which probably consist of some primitive fields or such.
e.g.
#Override
public int hashCode() {
return tiles != null ? tiles.hashCode() : 0;
}
See the docs here
int hashCode()
Returns the hash code value for this list. The hash code of a list is defined to be the result of the following calculation:
int hashCode = 1;
for (E e : list)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
This ensures that list1.equals(list2) implies that list1.hashCode()==list2.hashCode() for any two lists, list1 and list2, as required by the general contract of Object.hashCode().

HashMap not returning values based on key

I'm trying to use a HashMap with my class Cell as the key. However, after putting an item into the HashMap, calling contains on the item will return false.
public static void main(String args[]) {
HashMap<Cell, String> map = new HashMap<Cell, String>();
map.put(new Cell(0,0), "Bob");
System.out.println(map.containsKey(new Cell(0,0)));
System.out.println(new Cell(0,0).equals(new Cell(0,0)));
}
This prints out false and true, where it should print true and true, since according to the Map docs containsKey uses .equals(). What am I doing wrong?
This is most likely because you don't have equals() and hashCode() implemented. In Java, the rule of thumb is that if you implement one, you must implement the other. In your case, it's mandatory because HashMap makes use of them.
You created two separate objects with two separate addresses. Without these methods, the JVM has no way of knowing that the objects are "the same."
See http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()
Consider how a HashMap is implemented. When putting, it first calculates the object hashCode() to figure out which bucket to place the object in. When it tries to retrieve an object, it again gets its hashCode(), identifies to target bucket, goes through the linked list in the bucket, calling equals() against each object. It returns if it finds a match.
In other words, when you use HashMap you need to have a correct and matching implementation of equals() and hashCode().
The default hashCode() method inherited from Object does not correctly return the same hashCode() unless the object references are the same. In your case they are not.
calling new Cell(0,0) several times produce different objects with different hash Codes. You should implement hashCode for Cell class.
You likely forgot to implement the hashcode() function for Cell, which is also required in order to use a user defined class in a HashMap. Here is a simple and generally accurate way to implement a hashcode() function:
int hashcode(){
return (field1.toString()+field2.toString()+...+fieldN.toString()).hashcode();
}
Where field1 to fieldN are the fields in your class. If the fields are primatives (ie int) just take out the toString().

writing a public int compareTo() method java

I have an assignment where I need to create an arraylist of BookInventory objects with params (String bookNum, String bookTitle, int qoh, double bookPrice). Where bookNum is the hyphenated ISBN number of a book. After creating this array, I need to use the sort method of the Collections class. In my entity BookInventory class, I need to write a compareTo() that will end up sorting the arraylist by bookNum (which is a String). How do I do this? This is my first experience with this, and I don't understand.
This should get you started:
public class BookInventory implements Comparable<BookInventory> {
// code
public int compareTo(BookInventory other){
return bookTitle.compareTo(other.bookTitle);
}
//code
}
The thing to take away from this is to implement Comparable so that you can implement your own custom compareTo method thats automatically called when you sort an ArrayList.
To read more about compareTo and ordering, check out this:
http://download.oracle.com/javase/tutorial/collections/interfaces/order.html
The compareTo() method is used to compare two objects which have multiple properties.
It will return an integer to indicate which of the objects that was compared is larger.
It makes more sense if the objects being compared have properties which have a natural order.
Return value:
less than 0 -> indicates that the object is before the passed in object.
more than 0 -> the object is after the passed object
equal to 0 -> the two objects are at same level
If you look a the documentation for the Collections class, you will see that it implements two sort mwethods. One takes any kind of List together with a Comparator object for comparing elements of the list. The other takes a List of any kind of object that implements Comparable. Since compareTo is defined by Comparable (while a Comparator must implement compare), that tells you that your class must be declared as implements Comparable<BookInventory>, which means that it must have a compareTo method. See the documentation for Comparable.compareTo(T) for what your method must do. You will find the String method compareTo(String) to be useful.

How to compute the hashCode() from the object's address?

In Java, I have a subclass Vertex of the Java3D class Point3f. Now Point3f computes equals() based on the values of its coordinates, but for my Vertex class I want to be stricter: two vertices are only equal if they are the same object. So far, so good:
class Vertex extends Point3f {
// ...
public boolean equals(Object other) {
return this == other;
}
}
I know this violates the contract of equals(), but since I'll only compare vertices to other vertices this is not a problem.
Now, to be able to put vertices into a HashMap, the hashCode() method must return results consistent with equals(). It currently does that, but probably bases its return value on the fields of the Point3f, and therefore will give hash collisions for different Vertex objects with the same coordinates.
Therefore I would like to base the hashCode() on the object's address, instead of computing it from the Vertex's fields. I know that the Object class does this, but I cannot call its hashCode() method because Point3f overrides it.
So, actually my question is twofold:
Should I even want such a shallow equals()?
If yes, then, how do I get the object's address to compute the hash code from?
Edit: I just thought of something... I could generate a random int value on object creation, and use that for the hash code. Is that a good idea? Why (not)?
Either use System.identityHashCode() or use an IdentityHashMap.
System.identityHashCode() returns the same hash code for the given object as would be returned by the default method hashCode(), whether or not the given object's class overrides hashCode().
You use a delegate even though this answer is probably better.
class Vertex extends Point3f{
private final Object equalsDelegate = new Object();
public boolean equals(Object vertex){
if(vertex instanceof Vertex){
return this.equalsDelegate.equals(((Vertex)vertex).equalsDelegate);
}
else{
return super.equals(vertex);
}
}
public int hashCode(){
return this.equalsDelegate.hashCode();
}
}
Just FYI, your equals method does NOT violate the equals contract (for the base Object's contract that is)... that is basically the equals method for the base Object method, so if you want identity equals instead of the Vertex equals, that is fine.
As for the hash code, you really don't need to change it, though the accepted answer is a good option and will be a lot more efficient if your hash table contains a lot of vertex keys that have the same values.
The reason you don't need to change it is because it is completely fine that the hash code will return the same value for objects that equals returns false... it is even a valid hash code to just return 0 all the time for EVERY instance. Whether this is efficient for hash tables is completely different issue... you will get a lot more collisions if a lot of your objects have the same hash code (which may be the case if you left hash code alone and had a lot of vertices with the same values).
Please don't accept this as the answer though of course (what you chose is much more practical), I just wanted to give you a little more background info about hash codes and equals ;-)
Why do you want to override hashCode() in the first place? You'd want to do it if you want to work with some other definition of equality. For example
public class A {
int id;
public boolean equals(A other) { return other.id==id}
public int hashCode() {return id;}
}
where you want to be clear that if the id's are the same then the objects are the same, and you override hashcode so that you can't do this:
HashSet hash= new HashSet();
hash.add(new A(1));
hash.add(new A(1));
and get 2 identical(from the point of view of your definition of equality) A's.
The correct behavior would then be that you'd only have 1 object in the hash, the second write would overwrite.
Since you are not using equals as a logical comparison, but a physical one (i.e. it is the same object), the only way you will guarantee that the hashcode will return a unique value, is to implement a variation of your own suggestion. Instead of generating a random number, use UUID to generate an actual unique value for each object.
The System.identityHashCode() will work, most of the time, but is not guaranteed as the Object.hashCode() method is not guaranteed to return a unique value for every object. I have seen the marginal case happen, and it will probably be dependent on the VM implementation, which is not something you will want your code be dependent on.
Excerpt from the javadocs for Object.hashCode():
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)
The problem this addresses, is the case of having two separate point objects from overwriting each other when inserted into the hashmap because they both have the same hash. Since there is no logical equals, with the accompanying override of hashCode(), the identityHashCode method can actually cause this scenario to occur. Where the logical case would only replace hash entries for the same logical point, using the system based hash can cause it to occur with any two objects, equality (and even class) is no longer a factor.
The function hashCode() is inherited from Object and works exactly as you intend (on object level, not coordinate-level). There should be no need to change it.
As for your equals-method, there is no reason to even use it, since you can just do obj1 == obj2 in your code instead of using equals, since it's meant for sorting and similar, where comparing coordinates makes a lot more sense.

Categories

Resources