I ran into an interesting (and very frustrating) issue with the equals() method today which caused what I thought to be a well tested class to crash and cause a bug that took me a very long time to track down.
Just for completeness, I wasn't using an IDE or debugger - just good old fashioned text editor and System.out's. Time was very limited and it was a school project.
Anyhow -
I was developing a basic shopping cart which could contain an ArrayList of Book objects. In order to implement the addBook(), removeBook(), and hasBook() methods of the Cart, I wanted to check if the Book already existed in the Cart. So off I go -
public boolean equals(Book b) {
... // More code here - null checks
if (b.getID() == this.getID()) return true;
else return false;
}
All works fine in testing. I create 6 objects and fill them with data. Do many adds, removes, has() operations on the Cart and everything works fine. I read that you can either have equals(TYPE var) or equals(Object o) { (CAST) var } but assumed that since it was working, it didn't matter too much.
Then I ran into a problem - I needed to create a Book object with only the ID in it from within the Book class. No other data would be entered into it. Basically the following:
public boolean hasBook(int i) {
Book b = new Book(i);
return hasBook(b);
}
public boolean hasBook(Book b) {
// .. more code here
return this.books.contains(b);
}
All of a sudden, the equals(Book b) method no longer works. This took a VERY long time to track down without a good debugger and assuming the Cart class was properly tested and correct. After swaapping the equals() method to the following:
public boolean equals(Object o) {
Book b = (Book) o;
... // The rest goes here
}
Everything began to work again. Is there a reason the method decided not to take the Book parameter even though it clearly was a Book object? The only difference seemed to be it was instantiated from within the same class, and only filled with one data member. I'm very very confused. Please, shed some light?
In Java, the equals() method that is inherited from Object is:
public boolean equals(Object other);
In other words, the parameter must be of type Object. This is called overriding; your method public boolean equals(Book other) does what is called overloading to the equals() method.
The ArrayList uses overridden equals() methods to compare contents (e.g. for its contains() and equals() methods), not overloaded ones. In most of your code, calling the one that didn't properly override Object's equals was fine, but not compatible with ArrayList.
So, not overriding the method correctly can cause problems.
I override equals the following everytime:
#Override
public boolean equals(Object other){
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof MyClass)) return false;
MyClass otherMyClass = (MyClass)other;
...test other properties here...
}
The use of the #Override annotation can help a ton with silly mistakes.
Use it whenever you think you are overriding a super class' or interface's method. That way, if you do it the wrong way, you will get a compile error.
If you use eclipse just go to the top menu
Source --> Generate equals() and
hashCode()
Slightly off-topic to your question, but it's probably worth mentioning anyway:
Commons Lang has got some excellent methods you can use in overriding equals and hashcode. Check out EqualsBuilder.reflectionEquals(...) and HashCodeBuilder.reflectionHashCode(...). Saved me plenty of headache in the past - although of course if you just want to do "equals" on ID it may not fit your circumstances.
I also agree that you should use the #Override annotation whenever you're overriding equals (or any other method).
Another fast solution that saves boilerplate code is Lombok EqualsAndHashCode annotation. It is easy, elegant and customizable. And does not depends on the IDE. For example;
import lombok.EqualsAndHashCode;
#EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{
private long errorNumber;
private int numberOfParameters;
private Level loggingLevel;
private String messageCode;
See the options avaliable to customize which fields to use in the equals. Lombok is avalaible in maven. Just add it with provided scope:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
in Android Studio is
alt + insert ---> equals and hashCode
Example:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Proveedor proveedor = (Proveedor) o;
return getId() == proveedor.getId();
}
#Override
public int hashCode() {
return getId();
}
Consider:
Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...
the instanceOf statement is often used in implementation of equals.
This is a popular pitfall !
The problem is that using instanceOf violates the rule of symmetry:
(object1.equals(object2) == true) if and only if (object2.equals(object1))
if the first equals is true, and object2 is an instance of a subclass of
the class where obj1 belongs to, then the second equals will return false!
if the regarded class where ob1 belongs to is declared as final, then this
problem can not arise, but in general, you should test as follows:
this.getClass() != otherObject.getClass(); if not, return false, otherwise test
the fields to compare for equality!
recordId is property of the object
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Nai_record other = (Nai_record) obj;
if (recordId == null) {
if (other.recordId != null)
return false;
} else if (!recordId.equals(other.recordId))
return false;
return true;
}
Related
I ran into an interesting (and very frustrating) issue with the equals() method today which caused what I thought to be a well tested class to crash and cause a bug that took me a very long time to track down.
Just for completeness, I wasn't using an IDE or debugger - just good old fashioned text editor and System.out's. Time was very limited and it was a school project.
Anyhow -
I was developing a basic shopping cart which could contain an ArrayList of Book objects. In order to implement the addBook(), removeBook(), and hasBook() methods of the Cart, I wanted to check if the Book already existed in the Cart. So off I go -
public boolean equals(Book b) {
... // More code here - null checks
if (b.getID() == this.getID()) return true;
else return false;
}
All works fine in testing. I create 6 objects and fill them with data. Do many adds, removes, has() operations on the Cart and everything works fine. I read that you can either have equals(TYPE var) or equals(Object o) { (CAST) var } but assumed that since it was working, it didn't matter too much.
Then I ran into a problem - I needed to create a Book object with only the ID in it from within the Book class. No other data would be entered into it. Basically the following:
public boolean hasBook(int i) {
Book b = new Book(i);
return hasBook(b);
}
public boolean hasBook(Book b) {
// .. more code here
return this.books.contains(b);
}
All of a sudden, the equals(Book b) method no longer works. This took a VERY long time to track down without a good debugger and assuming the Cart class was properly tested and correct. After swaapping the equals() method to the following:
public boolean equals(Object o) {
Book b = (Book) o;
... // The rest goes here
}
Everything began to work again. Is there a reason the method decided not to take the Book parameter even though it clearly was a Book object? The only difference seemed to be it was instantiated from within the same class, and only filled with one data member. I'm very very confused. Please, shed some light?
In Java, the equals() method that is inherited from Object is:
public boolean equals(Object other);
In other words, the parameter must be of type Object. This is called overriding; your method public boolean equals(Book other) does what is called overloading to the equals() method.
The ArrayList uses overridden equals() methods to compare contents (e.g. for its contains() and equals() methods), not overloaded ones. In most of your code, calling the one that didn't properly override Object's equals was fine, but not compatible with ArrayList.
So, not overriding the method correctly can cause problems.
I override equals the following everytime:
#Override
public boolean equals(Object other){
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof MyClass)) return false;
MyClass otherMyClass = (MyClass)other;
...test other properties here...
}
The use of the #Override annotation can help a ton with silly mistakes.
Use it whenever you think you are overriding a super class' or interface's method. That way, if you do it the wrong way, you will get a compile error.
If you use eclipse just go to the top menu
Source --> Generate equals() and
hashCode()
Slightly off-topic to your question, but it's probably worth mentioning anyway:
Commons Lang has got some excellent methods you can use in overriding equals and hashcode. Check out EqualsBuilder.reflectionEquals(...) and HashCodeBuilder.reflectionHashCode(...). Saved me plenty of headache in the past - although of course if you just want to do "equals" on ID it may not fit your circumstances.
I also agree that you should use the #Override annotation whenever you're overriding equals (or any other method).
Another fast solution that saves boilerplate code is Lombok EqualsAndHashCode annotation. It is easy, elegant and customizable. And does not depends on the IDE. For example;
import lombok.EqualsAndHashCode;
#EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{
private long errorNumber;
private int numberOfParameters;
private Level loggingLevel;
private String messageCode;
See the options avaliable to customize which fields to use in the equals. Lombok is avalaible in maven. Just add it with provided scope:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
in Android Studio is
alt + insert ---> equals and hashCode
Example:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Proveedor proveedor = (Proveedor) o;
return getId() == proveedor.getId();
}
#Override
public int hashCode() {
return getId();
}
Consider:
Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...
the instanceOf statement is often used in implementation of equals.
This is a popular pitfall !
The problem is that using instanceOf violates the rule of symmetry:
(object1.equals(object2) == true) if and only if (object2.equals(object1))
if the first equals is true, and object2 is an instance of a subclass of
the class where obj1 belongs to, then the second equals will return false!
if the regarded class where ob1 belongs to is declared as final, then this
problem can not arise, but in general, you should test as follows:
this.getClass() != otherObject.getClass(); if not, return false, otherwise test
the fields to compare for equality!
recordId is property of the object
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Nai_record other = (Nai_record) obj;
if (recordId == null) {
if (other.recordId != null)
return false;
} else if (!recordId.equals(other.recordId))
return false;
return true;
}
I am, specifically concerned with obeying the symmetry part of the general contract established in Object#equals(Object) where, given two non-null objects x and y, the result of x.equals(y) and y.equals(x) should be the same.
Suppose you have two classes, PurchaseOrder and InternationalPurchaseOrder where the latter extends the former. In the basic case, it makes sense that comparing an instance of each against the other shall return consistently false for x.equals(y) and y.equals(x) simply because a PurchaseOrder is not always an InternationalPurchaseOrder and therefore, the additional fields in InternationalPurchaseOrder objects will not be present in instances of PurchaseOrder.
Now, suppose you upcast an instance of InternationalPurchaseOrder.
PurchaseOrder order1 = new PurchaseOrder();
PurchaseOrder order2 = new InternationalPurchaseOrder();
System.out.println("Order 1 equals Order 2? " + order1.equals(order2));
System.out.println("Order 2 equals Order 1? " + order2.equals(order1));
We already established that the result should be symmetric. But, should the result be false for cases when both object contain the same internal data? I believe the result should be true regardless of the fact that one object has an extra field. Since by upcasting order2, access to fields in InternationalPurchaseOrder class is restricted, the result of the equals() method shall be the result of the call to the super.equals(obj).
If all I stated is true, the implementation of the equals method in InternationalPurchaseOrder should be something like this:
#Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
// PurchaseOrder already passed check by calling super.equals() and this object is upcasted
InternationalPurchaseOrder other = (InternationalPurchaseOrder)obj;
if (this.country == null) {
if (other.country != null) return false;
} else if (!country.equals(other.country)) return false;
return true;
}
Assuming that country is the only field declared in this subclass.
The problem is in the super-class
#Override
public boolean equals (Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
PurchaseOrder other = (PurchaseOrder) obj;
if (description == null) {
if (other.description != null) return false;
} else if (!description.equals(other.description)) return false;
if (orderId == null) {
if (other.orderId != null) return false;
} else if (!orderId.equals(other.orderId)) return false;
if (qty != other.qty) return false;
return true;
}
Because the two instances are not of the same class. And if instead I use getClass().isAssignableFrom(Class), symmetry is lost. The same is true when using instanceof. In the book, Effective Java, Joshua Bloch indirectly warn about overriding equals unnecessarily. In this case, however, it is necessary for the subclass to have an overridden equals for instances to compare the fields declared in the subclass.
This has gone long enough. Is this a case for a more complicated implementation of the equals function, or is this simply a case against upcasting?
CLARIFICATION: This question is not answered by the suggestions proposed by #Progman in the comments section below. This is not a simple case of overriding equals methods for subclasses. I think the code I posted here shows that I have done this correctly. This post is SPECIFICALLY about the expected result of comparing two objects when one is upcasted so that it behaves like an object of the super-class.
I think the misconception here is that upcasting actually changes the type of an object.
Any type of casting is just a pointer to the compiler to treat a variable as having a certain type, for the developers benefit. So upcasting a variable with type InternationalPurchaseOrder to a PurchaseOrder might change the way you see it and can interact with it (as you mentioned, fields being restricted), but the variable still references an InternationalPurchaseOrder
Therefore to concretely answer your question, comparing classes will use the equals method declared to their actual type, not the type you have marked it as.
It turns out, the problem is a bit deeper than I originally thought for one main reason: Liskov Subsitution Principle.
My PurchaseOrder#equals(Object) method violates LSP. Because InternationalPurchaseOrder extends PurchaseOrder, it is correct to say that an international purchase IS-A purchase order. Therefore, to comply with LSP, I should be able to replace one instance for the other without any ill effects. Because of this, the line if (getClass() != obj.getClass()) return false; completely violates this principle.
Even when my focus was around upcasting (which by now I am pretty much convinced that it is a code smell), almost the same issue applies: Should I be able to compare instances of PurchaseOrder and InternationalPurchaseOrder and return true if they contain the same data internally? According to Joshua Bloch book Effective Java, Item 10 (Obey the general contract when overriding equals), the answer should be yes, but it is indeed no; but not for the reasons I initially thought (they are not the same type). The problem is this, and I quote:
There is no way to extend an instantiable class and add value component while preserving the equals contract, unless you're willing to forgo the benefits of object-oriented abstraction.
He continues to get in more details about this. The conclusion is simple: Do it this way and loose the benefits of abstraction, and violate LSP in the process. Or... simply follow a very important programming principle (especially in Java): favor composition over inheritance and inject the attributes you need to make a PurchaseOrder domestic or international in this example. In my case, this solution will look like this (irrelevant details of class omitted):
public class InternationalPurchaseOrder {
private PurchaseOrder po;
private String country;
public (PurchaseOrder po, String country) {
this.po = po;
this.country = country;
}
public PurchaseOrder asPurchaseOrder() {
return po;
}
#Override
public boolean equals (Object obj) {
if (!(obj instanceof InternationalPurchaseOrder)) {
return false;
}
InternationalPurchaseOrder ipo = (InternationalPurchaseOrder)obj;
return ipo.po.equals(po) && cp.country.equals(country);
}
}
This way, an InternationalPurchaseOrdercan continue to be compared to other instances of the same class AND if needed to be compared to objects of PurchaseOrder type, all that is needed is to call the asPurchaseOrder to return an object of the compatible type.
So, I have this class:
public class Book {
private int id;
private String name;
private Something somebody;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
if (id != book.id && somebody.getId() != book.somebody.getId()) return false;
return true;
}
#Override
public int hashCode() {
return id;
}
}
I would like to get all properties used in this class in equals method - in this case, I would get "id" from Book (since name is not used in equals method), and I would also get "somebody.id" since this is also used in equals method as sub object.
I need this info, so I can serialize only this properties and then during de-serialization on another machine use only that to compare equals. Otherwise it would be too cumbersome to compare full objects for equals (if I have too many sub-properties).
If you are using the Eclipse IDE, I know it has auto-complete options for equals and hashcode that will generate code including comparison of all declared fields. I am not sure if Netbeans or other IDEs have similar functionality, but would be surprised if commonly-used IDEs did not.
Also, your equals method should not compare fields of other objects but invoke equals on them:
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false; // instanceof is fast these days
Book book = (Book) o;
if (id == book.id &&
// either ensure these are not null or use java.util.Objects.equals()
somebody.equals(book.somebody))
return true;
return false;
}
Furthermore, if your hashcode is merely using id then perhaps your equals can as well. If your object is immutable, then id is all the comparison you would need, and would be quite a bit more efficient. If that is not the case, then it is typical that the checks used in equals are reflected in your hashcode to help prevent hash collisions.
I just came across a code using EqualsBuilder() in equals method. Is there any advantage of using it instead of writing (or generating from eclipse) our own logic? A simple example would be more helful.
Edit : If it doesn't have any benefits than having less code in the class, isn't there is overhead of reflection?
There are a few ways to approach this.
You can roll your own - that has the highest likelihood of getting
something subtle wrong.
You can have Eclipse generate your equals and hashCode methods for you - that leaves a lot of code in place, subject to inadvertent edits, and subject to failure to update when the class acquires a new field.
You can use EqualsBuilder; it avoids the aforementioned problems.
Best of all, at least in my experience, you can use lombok's EqualsAndHashCode annotation.
Using an EqualsBuilder is not implicitly better or worse than writing your equals method from scratch. In other words, I don't consider using EqualsBuilder to be a best practice.
A non-EqualsBuilder equals() method usually looks like this:
public boolean equals(Object other) {
boolean result;
if(this == other)
result = true;
else
if(other == null)
result = false;
else
if(other instanceof MyClass) {
MyClass o=(MyClass) other;
result = Objects.equals(this.a, o.a)
&& Objects.equals(this.b, o.b)
// ...
&& Objects.equals(this.z, o.z);
}
else
result = false;
return result;
}
I don't see need for example. Equals builder will generate exactly same code for you so the only difference is that you have less code in a class.
From my perspective it's better to write those methods (as you always have to override hashCode when you override equals)
Using ANYTHING except for your own implementation in equals() is GUARANTEED to be "worse" if you can use ... say a strictly unique ID.
If your ID is really unique you will most likely have the best, possible implementation with this, of course it needs to be polished quite a bit:
#Override
public boolean equals(Object other)
{
if(other instanceof MyClass)
{
MyClass obj = (MyClass)other;
return obj.getID() == this.getID();
}
else
return false;
}
Have a look at this, this and especially this
I ran into an interesting (and very frustrating) issue with the equals() method today which caused what I thought to be a well tested class to crash and cause a bug that took me a very long time to track down.
Just for completeness, I wasn't using an IDE or debugger - just good old fashioned text editor and System.out's. Time was very limited and it was a school project.
Anyhow -
I was developing a basic shopping cart which could contain an ArrayList of Book objects. In order to implement the addBook(), removeBook(), and hasBook() methods of the Cart, I wanted to check if the Book already existed in the Cart. So off I go -
public boolean equals(Book b) {
... // More code here - null checks
if (b.getID() == this.getID()) return true;
else return false;
}
All works fine in testing. I create 6 objects and fill them with data. Do many adds, removes, has() operations on the Cart and everything works fine. I read that you can either have equals(TYPE var) or equals(Object o) { (CAST) var } but assumed that since it was working, it didn't matter too much.
Then I ran into a problem - I needed to create a Book object with only the ID in it from within the Book class. No other data would be entered into it. Basically the following:
public boolean hasBook(int i) {
Book b = new Book(i);
return hasBook(b);
}
public boolean hasBook(Book b) {
// .. more code here
return this.books.contains(b);
}
All of a sudden, the equals(Book b) method no longer works. This took a VERY long time to track down without a good debugger and assuming the Cart class was properly tested and correct. After swaapping the equals() method to the following:
public boolean equals(Object o) {
Book b = (Book) o;
... // The rest goes here
}
Everything began to work again. Is there a reason the method decided not to take the Book parameter even though it clearly was a Book object? The only difference seemed to be it was instantiated from within the same class, and only filled with one data member. I'm very very confused. Please, shed some light?
In Java, the equals() method that is inherited from Object is:
public boolean equals(Object other);
In other words, the parameter must be of type Object. This is called overriding; your method public boolean equals(Book other) does what is called overloading to the equals() method.
The ArrayList uses overridden equals() methods to compare contents (e.g. for its contains() and equals() methods), not overloaded ones. In most of your code, calling the one that didn't properly override Object's equals was fine, but not compatible with ArrayList.
So, not overriding the method correctly can cause problems.
I override equals the following everytime:
#Override
public boolean equals(Object other){
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof MyClass)) return false;
MyClass otherMyClass = (MyClass)other;
...test other properties here...
}
The use of the #Override annotation can help a ton with silly mistakes.
Use it whenever you think you are overriding a super class' or interface's method. That way, if you do it the wrong way, you will get a compile error.
If you use eclipse just go to the top menu
Source --> Generate equals() and
hashCode()
Slightly off-topic to your question, but it's probably worth mentioning anyway:
Commons Lang has got some excellent methods you can use in overriding equals and hashcode. Check out EqualsBuilder.reflectionEquals(...) and HashCodeBuilder.reflectionHashCode(...). Saved me plenty of headache in the past - although of course if you just want to do "equals" on ID it may not fit your circumstances.
I also agree that you should use the #Override annotation whenever you're overriding equals (or any other method).
Another fast solution that saves boilerplate code is Lombok EqualsAndHashCode annotation. It is easy, elegant and customizable. And does not depends on the IDE. For example;
import lombok.EqualsAndHashCode;
#EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{
private long errorNumber;
private int numberOfParameters;
private Level loggingLevel;
private String messageCode;
See the options avaliable to customize which fields to use in the equals. Lombok is avalaible in maven. Just add it with provided scope:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
in Android Studio is
alt + insert ---> equals and hashCode
Example:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Proveedor proveedor = (Proveedor) o;
return getId() == proveedor.getId();
}
#Override
public int hashCode() {
return getId();
}
Consider:
Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...
the instanceOf statement is often used in implementation of equals.
This is a popular pitfall !
The problem is that using instanceOf violates the rule of symmetry:
(object1.equals(object2) == true) if and only if (object2.equals(object1))
if the first equals is true, and object2 is an instance of a subclass of
the class where obj1 belongs to, then the second equals will return false!
if the regarded class where ob1 belongs to is declared as final, then this
problem can not arise, but in general, you should test as follows:
this.getClass() != otherObject.getClass(); if not, return false, otherwise test
the fields to compare for equality!
recordId is property of the object
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Nai_record other = (Nai_record) obj;
if (recordId == null) {
if (other.recordId != null)
return false;
} else if (!recordId.equals(other.recordId))
return false;
return true;
}