Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this article, we explore generating equals() and hashCode() methods using the Eclipse IDE. We’ll illustrate how powerful and convenient the Eclipse’s code auto-generation is, and also emphasize that diligent testing of code is still necessary.

2. Rules

equals() in Java is used for checking if 2 objects are equivalent. A good way to test this is to ensure objects are symmetric, reflexive, and transitive. That is, for three non-null objects a, b, and c:

  • Symmetric – a.equals(b) if and only if b.equals(a)
  • Reflexive – a.equals(a)
  • Transitive – if a.equals(b) and b.equals(c) then a.equals(c)

hashCode() must obey one rule:

  • 2 objects which are equals() must have the same hashCode() value

3. Class With Primitives

Let’s consider a Java class composed of only primitive member variables:

public class PrimitiveClass {

    private boolean primitiveBoolean;
    private int primitiveInt;

    // constructor, getters and setters
}

We use the Eclipse IDE to generate equals() and hashCode() using ‘Source->Generate hashCode() and equals()‘. Eclipse provides a dialog box like this:

eclipse-equals-hascode

We can ensure all member variables are included by choosing ‘Select All’.

Note that the options listed beneath Insertion Point: affect the style of the generated code. Here, we don’t select any of those options, select ‘OK’ and the methods are added to our class:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (primitiveBoolean ? 1231 : 1237);
    result = prime * result + primitiveInt;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    PrimitiveClass other = (PrimitiveClass) obj;
    if (primitiveBoolean != other.primitiveBoolean) return false;
    if (primitiveInt != other.primitiveInt) return false;
    return true;
}

The generated hashCode() method starts with a declaration of a prime number (31), performs various operations on primitive objects and returns its result based on the object’s state.

equals() checks first if two objects are the same instance (==) and returns true if they are.

Next, it checks that the comparison object is non-null and both objects are of the same class, returning false if they are not.

Finally, equals() checks the equality of each member variable, returning false if any of them is not equal.

So we can write simple tests:

PrimitiveClass aObject = new PrimitiveClass(false, 2);
PrimitiveClass bObject = new PrimitiveClass(false, 2);
PrimitiveClass dObject = new PrimitiveClass(true, 2);

assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

4. Class With Collections and Generics

Now, let’s consider a more complex Java class with collections and generics:

public class ComplexClass {

    private List<?> genericList;
    private Set<Integer> integerSet;

    // constructor, getters and setters
}

Again we use Eclipse ‘Source->Generate hashCode() and equals()’. Notice the hashCode() uses instanceOf to compare class objects, because we selected ‘Use ‘instanceof’ to compare types’ in the Eclipse options on the dialog. We get:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((genericList == null)
      ? 0 : genericList.hashCode());
    result = prime * result + ((integerSet == null)
      ? 0 : integerSet.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (!(obj instanceof ComplexClass)) return false;
    ComplexClass other = (ComplexClass) obj;
    if (genericList == null) {
        if (other.genericList != null)
            return false;
    } else if (!genericList.equals(other.genericList))
        return false;
    if (integerSet == null) {
        if (other.integerSet != null)
            return false;
    } else if (!integerSet.equals(other.integerSet))
        return false;
    return true;
}

The generated hashCode() method relies on AbstractList.hashCode() and AbstractSet.hashCode() core Java methods. These iterate through a collection, summing hashCode() values of each item and returning a result.

Similarly, the generated equals() method uses AbstractList.equals() and AbstractSet.equals(), which compare collections for equality by comparing their fields.

We can verify the robustness by testing some examples:

ArrayList<String> strArrayList = new ArrayList<String>();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
        
ArrayList<String> strArrayListD = new ArrayList<String>();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet<Integer>(45,67));
        
assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

5. Inheritance

Let’s consider Java classes that use inheritance:

public abstract class Shape {
    public abstract double area();

    public abstract double perimeter();
}

public class Rectangle extends Shape {
    private double width;
    private double length;
   
    @Override
    public double area() {
        return width * length;
    }

    @Override
    public double perimeter() {
        return 2 * (width + length);
    }
    // constructor, getters and setters
}

public class Square extends Rectangle {
    Color color;
    // constructor, getters and setters
}

If we attempt the ‘Source->Generate hashCode() and equals()‘ on the Square class, Eclipse warns us that ‘the superclass ‘Rectangle’ does not redeclare equals() and hashCode() : the resulting code may not function correctly’.

Similarly, we get a warning about the superclass ‘Shape’ when we attempt to generate hashCode() and equals() on the Rectangle class.

Eclipse will allow us to plow forward despite warnings. In the case of Rectangle, it extends an abstract Shape class that cannot implement hashCode() or equals() because it has no concrete member variables. We can ignore Eclipse for that case.

The Square class, however, inherits width and length member variables from Rectangle, as well as it’s own color variable. Creating hashCode() and equals() in Square without first doing the same for Rectangle means using only color in equals()/hashCode():

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((color == null) ? 0 : color.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null)
            return false;
    } else if (!color.equals(other.color))
        return false;
    return true;
}

A quick test shows us that equals()/hashCode() for Square are not sufficient if it’s only the width that differs, because width is not included in equals()/hashCode() calculations:

Square aObject = new Square(10, Color.BLUE);     
Square dObject = new Square(20, Color.BLUE);

Assert.assertFalse(aObject.equals(dObject));
Assert.assertFalse(aObject.hashCode() == dObject.hashCode());

Let’s fix this by using Eclipse to generate equals()/hashCode() for the Rectangle class:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(length);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(width);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Rectangle other = (Rectangle) obj;
    if (Double.doubleToLongBits(length)
      != Double.doubleToLongBits(other.length)) return false;
    if (Double.doubleToLongBits(width)
      != Double.doubleToLongBits(other.width)) return false;
    return true;
}

We must re-generate equals()/hashCode() in the Square class, so Rectangle‘s equals()/hashCode() are invoked. In this generation of code, we’ve selected all the options in the Eclipse dialog, so we see comments, instanceOf comparisons, and if blocks:

@Override
public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + ((color == null) ? 0 : color.hashCode());
    return result;
}


@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!super.equals(obj)) {
        return false;
    }
    if (!(obj instanceof Square)) {
        return false;
    }
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null) {
            return false;
       }
    } else if (!color.equals(other.color)) {
        return false;
    }
    return true;
}

Re-running our test from above, we pass now because Square‘s hashCode()/equals() are calculated correctly.

6. Conclusion

The Eclipse IDE is very powerful and allows auto-generation of a boilerplate code – getters/setters, constructors of various types, equals(), and hashCode().

By understanding what Eclipse is doing, we can decrease time spent on these coding tasks. However, we must still use caution and verify our code with tests to ensure we have handled all the expected cases.

Code snippets, as always, can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.