Overriding the .equals() Method Tutorial

Before watching this tutorial I highly recommend watching my Overriding the .hashCode() method Tutorial as it important to understand what a hash code is and how to override that method. The Object class contains eleven methods; over half of those methods are somehow dependent upon other methods within the Object class to function properly. In a previous tutorial I discussed the relationship between the .wait() method and the .notify() and .notifyAll() methods. One of the most widely used methods, .equals(), shares a special relationship with the .hashcode() method. At this point you should already be familiar with invoking the .equals() method on an object. The purpose of this tutorial is to explain how to override the .equals() method.

Consider the following statements:
  String x = "ABC";
  String y = new String("ABC");
  System.out.println(x==y); // false
  System.out.println(x.equals(y)); // true
The first statement String x = "ABC"; creates a new string object in the String pool and assigns a reference to x. The second statement String y = new String("ABC"); creates a new string object in the heap and assigns a reference to y. The third statement uses the == operator, which when it used on an object returns whether one reference variable (x) points to the exact same object as another reference variable (y). They each refer to separate objects so the return value is false. By the way, you cannot "override" the == operator just in case you were wondering. The fourth statement uses the overridden .equals() method of the String class to determine whether one object state("ABC") is equivalent to another object state("ABC"). The two separate objects do indeed have the same state so the result is true (it's a little more complicated than that though).

Why would we want to override the inherited .equals() method from the Object class? That is an easy one, the .equals() method in the Object class looks like this:
public boolean equals(Object obj) {
     return (this == obj);
}
By default the .equals method is simply mimicking the functionality of the == operator. This method is just begging to be overridden into doing something useful!

The rules use big fancy words that you may be unfamiliar with. Don't let them intimidate you, each of them describe very simple concepts.

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.
  • Note: that it is generally necessary to override the hashCode() method whenever .equals() is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
You will learn that properly overriding the .equals() method is no trivial task. Let's jump right in and have some "fun."



Open the command prompt (CMD - see the Getting Started ) and type in the following commands.

C:\Windows\System32>cd \
C:\>md Java
C:\>cd Java
C:\Java>
C:\Java>md OverridingEquals
C:\Java>cd OverridingEquals
C:\Java\OverridingEquals>Notepad OverridingEquals.java

Copy and Paste, or type the following code into Notepad and be sure to save the file when you are done.


class OverridingEquals {
    public static void main(String args[]) {
        Intster x = new Intster(41);
        Intster y = new Intster(41);

        System.out.println("x = " + x.getState());
        System.out.println("x hashCode = " + x.hashCode());
        System.out.println("y = " + y.getState());
        System.out.println("y hashCode = " + y.hashCode());
        System.out.println();

        System.out.println("x==y " + (x==y));
        System.out.println("x.equals(y) " + x.equals(y));
        System.out.println();

        //StringBuilder sb = new StringBuilder("ABC");
        //System.out.println("x.equals(sb) " + x.equals(sb));

        //System.out.println("Reflexive = " + x.equals(x)); 
        //System.out.println("Symmetric = " + (x.equals(y) && y.equals(x)));
        //Intster z = new Intster(41);
        //System.out.println("Transitive = " + (x.equals(y) && y.equals(z) && x.equals(z)));
        //for(int i=0; i<5; i++) {
        //	System.out.println("Consistent = " + x.equals(y));
        //}
        //System.out.println("null = " + x.equals(null));
        //System.out.println("hashCode test = " + (x.hashCode() == y.hashCode()));
    }
}

class Intster { 
    private int state;

    Intster(int state) {
        this.state = state;
    }

    int getState() { return state; }
}

/*
class Intster { 
    private int state;

    Intster(int state) {
        this.state = state;
    }

    int getState() { return state; }

    */
    //Step 1 - start off by duplicating the equals method from the Object class
    /*@Override
    public boolean equals(Object obj) {
        return (this == obj);
    }*/

    //Step 2 - modify the return statement with the following if statement
    /*@Override
    public boolean equals(Object obj) {
        if (this == obj) { // both references refer to the same object! Easy peasy!
            return true;        
        }
        return false;
    }*/

    //Step 3 - check to see if we are dealing with an object that is an instance of Intster
    /*@Override
    public boolean equals(Object obj) {
        if (this == obj) { // both references refer to the same object! Easy peasy!
            return true;        
        }
        if(obj instanceof Intster) {
            return true; // we are not done yet!
        }
        return false;
    }*/

    //Step 4 - We are now ready to see if the state of the object parameter matches the state of current object
    /*@Override
    public boolean equals(Object obj) {
        if (this == obj) { // both references refer to the same object! Easy peasy!
            return true;        
        }
        if(obj instanceof Intster) {
            if(this.state == ((Intster)obj).getState()) { // downcast :)
                return true; // we are not done yet!
            }
        }
        return false;
    }*/

    //Step 5 - Do not forget to override the .hashCode() method and ensure hash codes match!!!!
    /*@Override
    public boolean equals(Object obj) {
        if (this == obj) { // both references refer to the same object! Easy peasy!
            return true;        
        }
        if(obj instanceof Intster) {
            if(this.state == ((Intster)obj).getState()) { // downcast 
                if(this.hashCode() == obj.hashCode()) {
                    return true; // we are done!!!
                }
            }
        }
        return false;
    }
	
    @Override
    public int hashCode() {
        // I'll keep it simple for the tutorial.
        // If you do not understand what is going on below,
        // you need to watch and understand my .hashCode override tutorial
        return state*1; 
    }*/
	
//}



Now switch back to the command prompt (CMD) and type in javac OverridingEquals.java and press Enter.
Now type in java OverridingEquals and press Enter.


C:\Java\OverridingEquals>javac OverridingEquals.java
C:\Java\OverridingEquals>java OverridingEquals
See video for results


Final thoughts

By now you should understand the overriding the .equals() method should not be done carelessly. Skipping or not understanding a single step can really come back to create some nasty bugs at runtime. If you understood why each step is necessary, then congratulations! However, if you are unsure about anything, watch the overriding hashCode tutorial and this tutorial again.


Tutorials