I have a class Odp. I want to use TreeSet to keep a sorted collection of Odp objects. However, I've been having problems.
public class OdpStorage {
private TreeSet<Odp> collection = new TreeSet<Odp>();
public addOdp(Odp o) {
return collection.add(o);
}
public int size() {
return collection.size();
}
}
collection.add(Odp o) is supposed to do nothing if it's already in the tree, right? Somehow, this unit test fails:
OdpStorage ts = new OdpStorage();
Odp ftw = new Odp("LOL");
Odp ktr = new Odp("OMG");
ts.addOdp(ftw);
ts.addOdp(ftw); //should do nothing
ts.addOdp(ftw); //should do nothing
ts.addOdp(ftw); //should do nothing
ts.addOdp(ktr);
assertEquals(2, ts.size());
The assertion fails. It expects 2, but the return value is 5. Why? Could the odp.equals() function be messed up?
Similarly, calling collection.contains(o) fails, even when the there is an object in the set X for which o.equals(X) returns true.
The .equals() function of Odp: (generated by Eclipse)
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Odp))
return false;
Gene other = (Odp) obj;
if (sequence == null) {
if (other.sequence != null)
return false;
} else if (!sequence.equals(other.sequence))
return false;
return true;
}
compareTo:
/**
* this = g0
* if they are equal, g1 is presumed to come first
*
* #return -1 if g0 comes before g1; 1 if g0 comes after g1
*/
#Override
public int compareTo(Odp g1) {
if (sequence.length() < g1.getSeq().length()) {
return -1;
}
else if (sequence.length() > g1.getSeq().length()) {
return 1;
}
if (sequence.compareTo(g1.getSeq()) < 0) {
return -1;
}
return 1;
}
hashCode() is not overridden. Problem?
UPDATE
hashCode() is as follows:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((sequence == null) ? 0 : sequence.hashCode());
return result;
}
But that still doesn't solve the problem.
Your compareTo implementation never returns 0. It should return 0 when the object instances are equal.
It appears that your collection.add(o) is failing to find the object in the backing TreeMap. Does your Odp implement Comparable or are you setting a default Comparable on your TreeSet whose compare method you have implemented? If so, you will need to ensure that your compareTo (for the Comparable), or your Comparator compare method will return 0 if the objects passed in are equals.
EDIT (in response to your comment to the original post):
It is recommended that you override HashCode() whenever you override equals()
EDIT2 in response to your compareTo implementation:
If g0 and g1 are equal, you should return 0. This is the root of the problem.
Mate cleanup your equals, its got too many if/elses. replace it with a nice do/while with lots of condition tests. If all the tests pass then reutrn true...Yes its got "goto" statements but its very easy to read and even easier to insert new conditions as necessary without lots of nesting. Nesting if/elses is evil. Using "elses" is evil and almost always never needed.
#Override
public boolean equals(final Object object) {
boolean equals = false;
do {
if (this == object) {
equals = true;
break;
}
if (false == super.equals(object)) {
break;
}
final DocumentView view = Unsafe.cast(object);
if (false == this.document.equals(view.document)) {
break;
}
if (this.revision != view.revision) {
break;
}
if (false == this.user.equals(view.user)) {
break;
}
if (false == this.timestamp.equals(view.timestamp)) {
break;
}
equals = true;
} while (false);
return equals;
}
Related
I have a class with a for-loop within the equals/hashCode:
class User {
private List<Task> tasks;
private ZonedDateTime date;
#Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + (date != null ? date() : 0);
for (var task : tasks) {
hash = 31 * hash + task.hashCode();
}
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final User other = (User) obj;
if (tasks.size() != other.tasks.size()) return false;
// needed?
for (int i = 0; i < tasks.size(); i++) {
if (!tasks.get(i).equals(other.tasks.get(i))) {
return false;
}
}
return Objects.equals(timeStamp, other.timeStamp) && Objects.equals(tasks, other. tasks);
}
}
I am used to have this version (version 2) of equals/hashCode, which is shorter and faster:
#Override
public int hashCode() {
return Objects.hash(date, tasks);
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final User other = (User) obj;
return Objects.equals(timeStamp, other.timeStamp) && Objects.equals(tasks, other. tasks);
}
Can I replace the former equals/hashCode with the version 2 without worrying about correctness?
Are both versions return the same result?
To sum up:
for typcial List implementation we can use version 2 instead of version 1.
One additional question related to this:
Will version 2 be also valid, if the property task is not a List but a Stream? (Stream<Task> tasks).
It depends on the specific List implementation.
Let's look at what Object.equals does:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
So it checks for a couple of trivial cases, then calls a.equals(b), which means that it will call the equals method of your list. But if you're using some custom List or just some list that doesn't compare the elements one by one, then the two implementation will be different.
For any sane implementation, equals should iterate over the elements and compare each one using equals. This is what AbstractList does.
Also note that your hash code will probably change between implementations.
Version 2 will work just fine, though it will return slightly different hash codes.
Lets say I have a class
public class Data{
public int k;
public int l;
public Data(int k, int l){
this.k = k;
this.l = l;
}
public boolean equals(Date m){
if(this.k == m.k && this.l = m.l)
return true;
return false;
}
}
And I add a few Data objects to a ArrayList:
ArrayList<Data> holder = new ArrayList<Data>;
Data one = new Data(0,0);
Data two = new Data(0,4);
Data three = new Data(0,5);
Why does indexOf not find this?:
holder.indexOf(new Data(0,4)); //returns -1
Is indexOf any better than going through the whole array list myself? Or am I missing something.
The indexOf() method does go through the entire list. Here's an excerpt from Java 7 source code:
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
It'd be better to let Java go through it than write it yourself. Just make sure that your equals method is sufficient at finding the object you want. You'll also want to override hashCode() as well.
I won't write your equals method out, but I would recommend that you at least:
Check for null
Test if the instances you're comparing are the same
You don't need to do if(boolean_expr) { return true; }; just return the boolean expression.
Make sure you're actually overriding your equals method - the signature of that requires an Object parameter, not Date.
The signature of your equals method is wrong. You are not overriding the equals in Object, but just overloading it.
To override the behavior of equals method in Object, your signature must exactly match with the one in Object. Try this:
public boolean equals(Object o) {
if(!(o instanceof Data)) return false;
Data other = (Data) o;
return (this.k == other.k && this.l == other.l);
}
In addition, as others suggested, it is a good idea to override hashCode method also for your object to work correctly in map based collections.
The answer from Makoto is right. The same i would say to. But you have some mistakes in your code above.
You wrote "public boolean equals(Date m){". I think, you meant Data instead of Date.
You wrote "if(this.k == m.k && this.l = m.l)". The second condition in if query have to be "==".
To your question:
Makoto's answer is one solution.
My solution is to use the help of eclipse to auto generate hashcode and equals methods. Like this:
public class Data {
// your class code here
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + k;
result = prime * result + l;
return result;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Data)) {
return false;
}
Data other = (Data) obj;
if (k != other.k) {
return false;
}
if (l != other.l) {
return false;
}
return true;
}
}
By convention you want to override hashcode also when you override equals
You will most probably find that the indexOf uses the hashcode method to match the object not the equals
If you use eclise to edit you code - eclipse will generate a good equals and hashcode method for you from the "source" menu.
public class state implements Comparator<state>{
Point a;
Point b;
private int path_cost=0;
...
}
class Point {
int x;
int y;
...
}
for above i have:
PriorityQueue<state> openNode= new PriorityQueue<state>();
LinkedList<state> closed =new LinkedList<state>();
state currNode;
I need to check if the Point a of ANY openNode or closed equals currNode's Point a.
I could use contains if i had to match the entire object but here i just care about one variabale (Point a) of state class. I want the method to check all the nodes in PriorityQueue and LinkedList.
addition:
I am thinking about using Iterator on my priorityQueue and LinkedList. But i am not sure how to read the value of Point a using Iterator.
EDIT: Looked like I'd misunderstood slightly. It's simpler than I thought.
// I've assumed more conventional names
Point currPoint = currNode.getPointA();
for (State openNode : openNodes) {
if (openNode.getPointA().equals(currPoint)) {
return true;
}
}
for (State closedNode : closedNodes) {
if (closedNode.getPointA().equals(currPoint)) {
return true;
}
}
// No matching points
return false;
You could potentially use Guava's Iterables.concat() method to make this slightly simpler:
for (State node : Iterables.concat(closedNodes, openNodes)) {
if (node.getPointA().equals(currPoint)) {
return true;
}
}
return false;
If you need to know which node has an equal point A, just change it to:
for (State node : Iterables.concat(closedNodes, openNodes)) {
if (node.getPointA().equals(currPoint)) {
return node;
}
}
return null;
That will only find one such node, of course - there may be multiple matches.
You will have to either provide equals method on Point a for state class or just use simple iteration and iterate over both List for comparison. contains method does the same.
If you use any other method it will be time consuming.
Very odd method is use Comparator to check equality
class PointAComparator implements Comparator<State>
{
Point p = null;
public PointAComparator(Point a) {
p = a;
}
#Override
public int compare(State o1, State o2) {
return (p.x == o1.a.x && p.y == o1.a.y) ? 1
: (p.x == o2.a.x && p.y == o2.a.y) ? 1 : -1;
}
}
Above compare method returns 1 for equal else -1 so when you do sorting then each list will have elements at the start which are equal. and then you can check for first element.
i used method overriding on function equals for both the object and achieved my result.
class Point {
int x;
int y;
...
#Override
public boolean equals(Object other){
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof Point))return false;
Point otherPoint = (Point)other;
return (this.x==otherPoint.getX() && this.y==otherPoint.getY() )? true : false;
}
}
public class state implements Comparator<state>{
Point a;
Point b;
private int path_cost=0;
...
#Override
public boolean equals(Object other){
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof state))return false;
state otherState = (state)other;
return ((this.a).equals(otherState.a))? true : false;
}
}
I have a POJO that contains hashcode() and equals() method that I have overridden , but my query is that what about If i make hashcode() method comment then in collection lets say in a hashmap when I am storing the user defined objects then what impact would it have...and another thing is that if I make equals method as a comment then what Impact would it have If I try to enter duplicate records will it store duplicate records twice!
public class Employee {
String name,job;
int salary;
public Employee(String n , String j, int t )
{
this.name= n;
this.job=j;
this.salary= t;
}
/* #Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((job == null) ? 0 : job.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + salary;
return result;
}*/
/*#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (job == null) {
if (other.job != null)
return false;
} else if (!job.equals(other.job))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (salary != other.salary)
return false;
return true;
}
*/
#Override
public int hashCode()
{
return name.hashCode()+job.hashCode()+salary;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
{
return true;
}
// make sure o can be cast to this class
if (obj == null || obj.getClass() != getClass())
{
// cannot cast
return false;
}
Employee e = (Employee) obj;
return this.name.equals(e.name)&&this.job.equals(e.job)&&this.salary==e.salary;
}
#Override
public String toString() {
return name+"\t" +"\t"+ job +"\t"+ salary;
}
}
If you leave out equals, it will use the Object.equals method, which is true only if both objects are the same instance.
Thus, duplicate objects won't be added, but duplicate records may be added, if you add the same data through different object instances.
Once you comment something out, it isn't compiled and won't have any effect on the rest of your program.
Also, not overriding the Object.equals() method means that equals() will only return true when the two objects are the same instance. For example:
int[] data = data; //data to create object
mObject o1 = new mObject(data); //new object from data
mObject o2 = new mObject(data); //another object from the same data
System.out.println(o1.equals(o2)); //prints false even though o1 and o2 contain the same information.
No matter what you do or don't comment out, the same object (address-wise) can't be added twice to a set. (For any sane definition of equals() and hashCode()).
When you have two objects o1 and o2 that "mean" the same thing (have the same data):
If you comment out hashCode(), you (almost always) will be able to add both to the same HashSet, even if equals() says that the two objects are the same (or as keys to the same HashMap). This is because all the hash-based data structures compare objects by hashcode first then by equals().
If you comment out equals() without commenting hashCode(), all collections will behave as if the objects are not equal. This is because even the hash-based comparisons check equals() after checking hashCode(), since hash collisions can occur even in proper implementations of hashCode() and equals().
I'm trying to use HashSet to store objects of a class that I created, but apparently the same objects seem to have two different hashes, which is why the contains method does not realize that the object is already in the HashSet. This leads to my program running out of heap memory.
I don't think I'm doing anything wrong, but I wanted a second opinion anyway. I've done similar operations before which all worked fine, which makes this particularly annoying. I'd appreciate any help.
Here's my code
move1 = new Move(t,s);
if(move1.hashCode()==new Move(t,s).hashCode())
System.out.println("match");
move2 = new Move(s,t);
moves.add(move1);
moves.add(move2);
if(moves.contains(new Move(t,s)))
System.out.println("match found");
Here's the Move class:
public class Move {
private int move1;
private int move2;
Move(int m1, int m2)
{
move1 = m1;
move2 = m2;
}
public String toString()
{
return String.valueOf(move1)+" "+String.valueOf(move2);
}
}
Here's the output I get
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.addEntry(HashMap.java:797)
at java.util.HashMap.put(HashMap.java:431)
at java.util.HashSet.add(HashSet.java:194)
at makeMove.<init>(makeMove.java:33)
You need to override the Object#hashCode() method in the Move class to let it return the same hashCode() value for the state of the Move instance. Don't forget to override Object#equals() as well.
See also:
Overriding equals and hashCode in Java
Hint: if you're using an IDE like Eclipse, you can also just autogenerate them. Rightclick somewhere the Move class, choose Source > Generate hashCode() and equals(). Here is how it look like then:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + move1;
result = prime * result + move2;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Move other = (Move) obj;
if (move1 != other.move1)
return false;
if (move2 != other.move2)
return false;
return true;
}
HashSet will determine equality based on calling hashCode() and equals(). You have not implemented these, so you'll inherite them from Object. The hashCode and equals methods of Object is just based on whether the references are equal.
That's why if(move1.hashCode()==new Move(t,s).hashCode()) is false. move1 is a different instance than the instance created by calling new Move(t,s).hashCode()
You'll need to implement hashCode and equals in your Move class.
e.g.(though perhaps non-optimal, and you might want a null safe equals - have your IDE generate them if it can)
public int hashCode() {
return move1 ^ move2 +;
}
public boolean equals(Object o) {
if(!other instanceof Move)
return false;
Move other = (Move)o;
return other.move1 == move1 && other.move2 == move2;
}
You have to override equals() and hashCode().
This may be an option.
import static java.lang.System.out;
public class Move {
private int move1;
private int move2;
Move(int m1, int m2) {
move1 = m1;
move2 = m2;
}
public String toString() {
return String.valueOf(move1)+" "+String.valueOf(move2);
}
public int hashCode() {
return move1 * 31 + move2 * 31;
}
public boolean equals( Object other ) {
if( this == other ) { return true; }
if( other instanceof Move ) {
Move m2 = ( Move ) other;
return this.move1 == m2.move1 && this.move2 == m2.move2;
}
return false;
}
public static void main( String [] args ) {
out.println( new Move(2,3).equals( new Move(2,3)));
out.println( new Move(1,1).hashCode() == new Move(1,1).hashCode() );
}
}
You have to define if the order of the move is relevant ( 1,2 isequals to 2,1 or not )
For more information:
What issues should be considered when overriding equals and hashCode in Java?