I am currently revising for an upcoming exam and I'm confident in saying that I will most likely get a question that will require me to implement equals method, compareTo method and hashCode method. It is open book therefore I want to try and take full advantage of this because I think it could be especially useful for this area. For example, I managed to implement the compareTo method by following someone elses code and then adjusting it, I want this advantage in the exam as others will = )
Anyway, I have a sample exam question given to us and it asks us exactly this. I'm attempting to implement the equals method whilst using other peoples code as examples because I will be taking these in with me as a template in case they do show up, since it is allowed. However, the equals method doesn't seem as easy to implement through a template as the compareTo. I have attempted it but it doesn't seem to be going great =D I was hoping you guys could help me out on where I'm going wrong, I think although I'm not certain it could be possibly my If statement. Finally, if you have any tips for any of the methods above in an open book exam then I'll extremely appreciate it! As I'm really worried/uncertain whether or not I'll pass = )
Anyway I have been given the following code :
public class Books {
private final String title;
private final String author;
private final int edition;
public Books(String title, String author, int edition)
{
this.title = title;
this.author = author;
this.edition = edition;
}
public String getTitle(){
return title;
}
public String getAuthor(){
return author;
}
public int getEdition(){
return edition;
}
With this code I'm to implement the equals method and here's the attempt I made at doing it whilst following someone elses.
public boolean equals(Object obj){
if (this == obj)
return true;
if (obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Books other = (Books) obj;
if (getTitle() == null){
if (other.getTitle() != null)
return false;
}
else if
(!getTitle().equals(other.getTitle()))
return false;
return true;
else if (getAuthor() == null){
if (other.getAuthor() != null)
return false;
}
else if
(!getAuthor().equals(other.getAuthor()))
return false;
return true;
if (getEdition() != other.getEdition())
return false;
}
I know I've pretty much screwed how the if statement flows, I struggled to follow it through nicely as it's grown =/
Assuming we are using Java 15 (with all the preview stuff activated), my implementation would look like this:
public boolean equals( Object obj )
{
var retValue = this == obj;
if( !retValue && (obj instanceof Book other) && (getClass() == obj.getClass()) )
{
retValue = Objects.equals( getTitle(), other.getTitle() );
retValue &= Objects.equals( getAuthor(), other.getAuthor() );
retValue &= getEdition() == other.getEdition();
}
return retValue;
}
If the class Book is final, the check … && (getClass() == obj.getClass()) could be omitted without a different result (until someone changes that and extends Book to Atlas – without that check, an Atlas could be equal to a Book, but no Book could be equal to any Atlas. This would hurt the requirement that any implementation of Object.equals() needs to be symmetric).
And of course, the retValue &= … constructs could be replaced by mere … && ….
Funny thing: the Java Collection Classes do not check the class:
List<String> a = new ArrayList<>();
List<String> l = new LinkedList<>();
List<String> e = Collections.emptyList();
out.println( a.equals( l ) ); // true
out.println( a.equals( e ) ); // true
out.println( l.equals( a ) ); // true
out.println( l.equals( e ) ); // true
out.println( e.equals( a ) ); // true
out.println( e.equals( l ) ); // true
e.getClass().getName() will return (for Java 15) java.util.Collections$EmptyList. So symmetry does not necessarily require that both objects do have the same implementation.
For java.util.List this is even mentioned in the documentation: "[…]two lists are defined to be equal if they contain the same elements in the same order."
This is what Eclipse produced for that class:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Books other = (Books) obj;
if (author == null) {
if (other.author != null)
return false;
} else if (!author.equals(other.author))
return false;
if (edition != other.edition)
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
to override user object specific equals method usually we should do following, my comments in line
#Override
public boolean equals(Object obj) {
//check if object received is not null
if (obj == null) {
return false;
}
//check if the object received is of the same class
if (getClass() != obj.getClass()) {
return false;
}
final Beta other = (Beta) obj;
//compare all properties of the class and return true based on oure requirement
if ((this.getSample() == null) && (other.getSample() == null)){
return true;
}
if ((this.getSample().getId().equals(other.getSample().getId())) && (this.getSample().getName().equals(other.getSample().getName()))) {
return true;
}
//if nothing suits well based on our requirement we can directly send false or handle the responsibility of comparison to super class
return super.equals();
}
Related
I'm having some problems with the equals method generated by Eclipse.
Suppose I have an Entity Bean with the attributes entityId and name, but I just selected for the equals generation the entityId attribute. So, the code generated by eclipse is the following:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entity other = (Entity) obj;
if (entityId == null) {
if (other.entityId != null)
return false;
} else if (!entityId.equals(other.entityId))
return false;
return true;
}
The problem is that when comparing two different instances of the class Entity that have null as the entityId, the equals method returns true.
For me, this equals implementation is not correct (at least when using it with JPA), because two entities without an entityId are just object that are going (probably) to be persisted as new objects in a database. If I add these two objects to a Set (one to many relationship, for example), after the two insertions the Set is going to have just one element (Sets don't allow duplicates).
So, the question is why Eclipse generates the equals method like this? Do you think is better to implement the equals method with the following code?
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entity other = (Entity) obj;
if (entityId == null) {
if (other.entityId != null)
return false;
else
return true;
} else if (!entityId.equals(other.entityId))
return false;
return true;
}
Eclipse simply doesn't know about how you will use your class.
Usually if fields have equal values objects considered equal
class Human {
String name;
String petName;
}
Human("Bob", null) is equal to Human("Bob", null).
You case is somewhat special, so you have to made adjustment by yourself.
I was recently overriding some equals methods in domain objects of my recent Java project. As we are using Sonar to calculate our code metrics, I immediately saw the cyclomatic complexity of these classes increasing above a threshold.
I'm wondering if there is a clever way, pattern or option at all to keep this metric low although having a little more complex equals method.
EDIT: Here is one of my examples that I have, nothing really specific I would say, just so that we know what we are talking about.
#Override
public boolean equals(Object o) {
if (o instanceof MyKey) {
MyKey other = (MyKey) o;
if (this.foo.longValue() == other.getFoo().longValue() &&
this.bar.equalsIgnoreCase(other.getBar()) &&
this.foobar.shortValue() == other.getFoobar().longValue()){
return true;
}
}
return false;
}
#Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + foo.hashCode();
hash = 53 * hash + bar.hashCode();
hash = 53 * hash + foobar.hashCode();
return hash;
}
You could use Apache's EqualsBuilder:
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
You didn't but you should always check for nulls. foo could be null, resulting in a NullPointerException.
this.foo.longValue() == other.foo.longValue()
Luckily Objects utility class saves you from a lot of problems as it automatically checks for nulls.
#Override
public boolean equals(Object object) {
if (object == null)
return false;
if (!(object instanceof MyObject))
return false;
MyObject other = (MyObject) object;
//#formatter:off
return Objects.equals(getX(), other.getX()) &&
Objects.equals(getY(), other.getY()) &&
Objects.equals(getZ(), other.getZ()));
//#formatter:on
}
#Override
public int hashCode() {
return Objects.hashCode(getX(), getY(), getZ());
}
If the fields to check are a lot you can optionally add this at the beginning of the equals method.
if (object == this)
return true;
In theory it can save some computation in some edge case.
The only thing that really helps, in my opinion, is good indentation. I always wrap those line between a pair of //#formatter:off and //#formatter:on. It's boilerplate code, anyway: very easy to write, very easy to make mistakes.
In your case, though, you're checking equality using equalsIgnoreCase. It's a pity Objects doesn't have such a method. You can build your own pretty easily.
public final class Strings {
public static boolean equalsIgnoreCase(String a, String b) {
return a == null ? b == null : a.equalsIgnoreCase(b);
}
private Strings() {
}
}
And use it like this
return Objects.equals (getX(), other.getX()) &&
Strings.equalsIgnoreCase (getY(), other.getY()) &&
Objects.equals (getZ(), other.getZ()));
I'm fairly new to java and am just trying to get my head around understanding #Override of the equals() and hashcode() methods.
I know for the equals method to be correct it needs to be:
Reflexive: a.equals(a)
Symmetric: a.equals(b) then b.equals(a)
Transitive: a.equals(b) && b.equals(c) Then a.equals(c)
Not null: ! a.equals(null)
I am struggling to pinpoint which of the above properties I am and am not satisfying when writing my overide of the equals method.
I am aware that eclipse can generate these for me, however as I haven't yet gotten the concept fully, writing it out helps me to learn.
I have written out the what I think is the correct way to do it, but when I check with the eclipse generated version I seem to be 'missing' some aspects.
Example:
public class People {
private Name first; //Invariants --> !Null, !=last
private Name last; // !Null, !=first
private int age; // !Null, ! <=0
...
}
What I wrote:
public boolean equals(Object obj){
if (obj == null){
return false;
}
if (!(obj instanceof People)){
return false;
}
People other = (People) obj;
if (this.age != other.age){
return false;
}
if (! this.first.equals(other.first)){
return false;
}
if (! this.last.equals(other.last)){
return false;
}
return true;
}
vs eclipse generated
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
People other = (People) obj;
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
if (age != other.age)
return false;
if (last == null) {
if (other.last != null)
return false;
} else if (!last.equals(other.last))
return false;
return true;
}
I am missing:
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
And for each variable:
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
I'm not sure what getClass() is and is my implmentation incorrect?
First piece of code:
if (this == obj)
return true;
This improves performance in case you compare the object reference against itself. Example: a.equals(a);.
Second piece of code:
if (getClass() != obj.getClass())
return false;
This compares if the class of the reference being compared is the same class of this. The difference between using this approach and instanceof is that it's more restrictive when comparing against a sub class. Example:
public class Foo { }
public class Bar extends Foo { }
//...
Foo foo = new Bar();
System.out.println(foo instanceof Bar); //prints true
System.out.println(foo instanceof Foo); //prints true
Foo foo2 = new Foo();
System.out.println(foo.getClass() == foo2.getClass()); //prints false
Which one should you choose? There's no good or bad approach, it will depend on your desired design.
Third piece of code:
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false; //For each variable.
This is simply a null check for each object reference field in the class. Note that if this.first is null then doing this.first.equals(...) will throw a NullPointerException.
I don't think your implementation is incorrect, but a few notes:
if (this == obj)
return true;
Is a performance optimization, it directly tests for reference equality and short-circuits tests where a is a.
if (getClass() != obj.getClass())
return false;
Is similar to your instanceof call, optimizes away a null check. The other calls seem to be null-checks.
You don't need to write
if (obj == null){
return false;
}
if (!(obj instanceof People)){
return false;
}
because null always gives false in instanceof checks. So these lines can be simplified to just
if (!(obj instanceof People)){
return false;
}
As for your main question of whether your method meets the requirements for an equals() method, strictly speaking the answer is no, or at least it's potentially dodgy. This is because it would be possible to extend the class as follows
public class SpecialPeople extends People {
// code omitted
#Override
public boolean equals(Object object) {
if (object == null || object.getClass() != getClass())
return false;
SpecialPeople other = (SpecialPeople) object;
return other.getAge() == getAge()
&& other.getFirst().equals(getFirst())
&& other.getLast().equals(getLast());
}
Now suppose a is an instance of People and b is an instance of SpecialPeople. Suppose also that a and b have the same name and age. Then
a.equals(b) == true // instanceof check succeeds
b.equals(a) == false // getClass() check fails
Therefore equals() is not symmetric! For this reason, if you are using instanceof rather than getClass() in equals() you should probably either make the equals() method final or the class final.
I am working on existing project. I have below user class's equals method. when I compare equality with equals I get false on
if (getClass() != other.getClass()) {
return false;
}
The coomplete equals code:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
User other = (User) obj;
if (getClass() != other.getClass()) {
return false;
}
if (this.getUserId() == null) {
if (other.getUserId() != null) {
return false;
}
} else if (!this.getUserId().equals(other.getUserId())) {
return false;
}
return true;
}
Do I require to check the class type here? If yes, why are my user classes are different? Why can not I put to check class type like below?
if (!(obj instanceof User)){
return false;
}
if (getClass() != other.getClass()) {
return false;
}
This check should be placed before the casting:
User other = (User) obj;
The difference between instanceof and getClass() is that the latter ensures that the type is not a sub-type. So:
User user = new SubUser();
boolean a = user instanceof User;
boolean b = user.getClass() == User.class;
System.out.println(a);
System.out.println(b);
would print:
true
false
You should use the instanceOf method to avoid a ClassCastException later. This Exception will be thrown if you use the equals method with the wrong object type.
First of all, I think you should inverse the lines
User other = (User) obj;
if (getClass() != other.getClass()) {
return false;
}
to become
if (getClass() != other.getClass()) {
return false;
}
User other = (User) obj;
Secondly, the equals method is an important one in the java collections library and in a lot of others as well so you should really think about any implementation details.
Suppose you have an Employee class (with an id) being subclassed into Manager, so you might consider writing an equals method on Employee just checking the id and you are fine. But, are you ? It depends.
So if you check in employee equals method it will return true if you pass a manager. But if you check on class equality by using getClass, it will return false when you pass a manager.
Suppose these 2 classes are stored in a database into an employee table and into a manager table respectively, the database has the id as a column which is defined as as an autoincrement column. Which means you can have an employee with id 100 who is a totally different person than a manager with id 100.
On the other hand you can have a single employee table storing all employees and managers. Hence here if you have an employee object with id 100 and a manager with id 100 then they must be the same person.
For your question, you should put
getClass() != other.getClass()
before
User other = (User) obj;
Equqality is an interesting question. And many people discussed about it. One detail dicussion on equality can be seen in Programming in Scala 2nd Chapter30.
The Effective Java book by Joshua Bloch provides very good suggestions to implement many, what one might consider, standard situations. It includes an approach to implementing the equals method.
Here is a modified implementation:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof User)) { // this covers the null check
return false;
}
User other = (User) obj; // this is now safe
if ((this.getUserId() == null) && other.getUserId() != null) {
return false
} else if ((this.getUserId() != null) && !this.getUserId().equals(other.getUserId())) {
return false;
}
return true;
}
It is not so obvious, but the instanceof check returns false for null values simply because there is no way identify their type (i.e. nulls are untyped).
As I found, User class from hibernate layer/DAO is not a direct User class. It's sub class of User or a proxy. So, When I check with obj.getClass() it gives false.
In such cases, better not comparing with getClass().
I checked with instanceof.
here you stated that you don't have any subclasses of User so you can use instanceof check.
Let's say a class Member where member has an Id of type String. I want to know if there might be any problem with using a String equals() implementation inside another implementation. Would it be any better if the field Id was of type Long.
#Override public boolean equals(Object object){
if(object == null) return false;
if(! (object instanceof Member)) return false;
Member member= (Member) object;
if(this.Id.equals(member.Id)) //<==My concern is here
return true;
else
return false;
}
The only problem I see is that you may have more class members besides id and your equals implementation will say true even while two instances differ greatly but have the same id. With this in mind, make sure to have a consistent hashCode implementation alongside to avoid inconsistencies.
Another idea is to define custom comparators e.g.
static Comparator<Member> MEMBER_ID_COMPARATOR = new Comparator<Member>() {
#Override
public int compare(Member first, Member second) {
assert(first.getId() != null);
assert(second.getId() != null);
return first.getId().compareTo(second.getId());
}
}
No problem at all. From this point of view, leave Id as a String. If it's only going to contain numbers, yes, you can use Long or BigInteger (my preference). But this is a different animal whatsoever.
Try this instead
#Override public boolean equals(Object object){
if(object == null) return false;
if(! (object instanceof Member)) return false;
Member member= (Member) object;
if (this.Id == null && member.Id == null)
return true;
else if (this.Id != null && member.Id != null)
return this.Id.equals(member.Id);
else
return false;
}