How to add a foolproof equals method to a Java class

Some background

Let’s recall some facts relevant to our task:

  • The class Object is an ancestor to all other Java classes
  • Object defines the method equals
  • Object’s method equals compares the addresses of two objects
  • Every Java class inherits Object’s version of the method equals
  • An equals method in a Java class must override Object’s equals method
  • A non-static method in a subclass overrides a method in the superclass if the methods have the same headers—that is, the same name, return type, and parameters.

An equals method for a class of names

Let’s define an equals method for the simple class of names below:

/** Name.java by F. M. Carrano
    A class of names, each containing a first name and a last name.
*/
public class Name
{
   private String first; // First name
   private String last;  // Last name
	
   /** Creates a name having first and last names.
       @param firstName  A string giving the name's first name.
       @param lastName  A string giving the name's last name. */
   public Name(String firstName, String lastName)
   {
      first = firstName;
      last = lastName;
   } // End constructor
	
   /** @return the entire name as a string. */
   public String getName()
   {
      return first + " " + last;
   } // End getName
	
   /**  @return the entire name as a string. */
   public String toString()
   {
      return getName();
   } // End toString
} // End Name

Requirement

Name's equals method must override Object’s version, so both methods must have identical headers. The header for the equals method in Object is:

public boolean equals(Object other) // Object's version

First attempt

The following definition of equals seems to accomplish our goal: it will override the equals method in Object because the headers of both methods are the same.

/** Tests whether this name is equal to another name.
    @param other  Another Name object.
    @return true if the names are equal, otherwise false. */
public boolean equals(Object other)
{
   Name otherName = (Name)other;
   return first.equals(otherName.first) && last.equals(otherName.last);
} // End equals

Notice that we need to cast the other parameter from Object to Name so that we can access Name’s data fields (first and last).

Testing the method

The following version of the Name class contains the previous definition of equals and also defines a main method to test equals. Click the RUN button below to see what happens:

/** Name.java by F. M. Carrano
A class of names, each containing a first name and a last name.
*/
public class Name
{
private String first; // First name
private String last; // Last name
/** Creates a name having first and last names.
@param firstName A string giving the name's first name.
@param lastName A string giving the name's last name. */
public Name(String firstName, String lastName)
{
first = firstName;
last = lastName;
} // End constructor
/** @return the entire name as a string. */
public String getName()
{
return first + " " + last;
} // End getName
/** @return the entire name as a string. */
public String toString()
{
return getName();
} // End toString
/** Tests whether this name is equal to another name.
@param other Another Name object.
@return true if the names are equal, otherwise false. */
public boolean equals(Object other)
{
Name otherName = (Name)other;
return first.equals(otherName.first) && last.equals(otherName.last);
} // End equals
public static void main(String[] args)
{
Name jamie1 = new Name("Jamie", "Java");
Name jamie2 = new Name("Jamie", "Java");
Name jamie3 = jamie1;
Object jamie4 = jamie1;
Object jamie5 = new Name("Jamie", "Java");
Name montana = new Name("Montana", "Oakely");
System.out.println("jamie1's name is " + jamie1);
System.out.println("jamie2's name is " + jamie2);
System.out.println("jamie3's name is " + jamie3);
System.out.println("jamie4's name is " + jamie4);
System.out.println("jamie5's name is " + jamie5);
System.out.println("montana's name is " + montana);
System.out.println();
System.out.println("According to the equals method:");
if (jamie1.equals(montana))
System.out.println("  jamie1 and montana are equal.");
else
System.out.println("  jamie1 and montana are not equal.");
if (jamie1.equals(jamie2))
System.out.println("  jamie1 and jamie2 are equal.");
else
System.out.println("  jamie1 and jamie2 are not equal.");
if (jamie1.equals(jamie1))
System.out.println("  jamie1 and jamie1 are equal.");
else
System.out.println("  jamie1 and jamie1 are not equal.");
if (jamie1.equals(jamie3))
System.out.println("  jamie1 and jamie3 are equal.");
else
System.out.println("  jamie1 and jamie3 are not equal.");
if (jamie1.equals(jamie4))
System.out.println("  jamie1 and jamie4 are equal.");
else
System.out.println("  jamie1 and jamie4 are not equal.");
if (jamie1.equals(jamie5))
System.out.println("  jamie1 and jamie5 are equal.");
else
System.out.println("  jamie1 and jamie5 are not equal.");
if (jamie1.equals("Jamie Jones"))
System.out.println("  jamie1 and the string \"Jamie Jones\" are equal.");
else
System.out.println("  jamie1 and the string \"Jamie Jones\" are not equal.");
} // End main
} // End Name

Our method appears to give us correct results for the Name objects defined in lines 41 - 46 of the previous program, but we get an exception when the statement at line 88 invokes equals. That statement is:

if (jamie1.equals("Jamie Jones"))

We are passing a String object instead of a Name object to the equals method, but we didn’t get a syntax error. Why not? The method’s parameter other has the data type Object, and so it can accept an object of any type!

The exception will actually occur at line 35 of Name’s equalsmethod. That line is:

Name otherName = (Name)other;

Since other references a String object, it can’t be cast to a Name object; so, we will get an exception (the ClassCastException) that is fatal.

Fixing the method

To ensure that the argument passed to the equals method is an object of the same class as the invoking object, use the Java operator instanceof. For example, the expression
  other instanceof Name
is true if other references an object of either the class Name or a class derived from Name. Otherwise, the expression will be false. Thus, our revised equals method appears as follows:

public boolean equals(Object other)
{
   boolean result = false;
   if (other instanceof Name)
   {
      Name otherName = (Name)other;
      result = first.equals(otherName.first) &&
last.equals(otherName.last);
   } // End if

   return result;
} // End equals

See how the method behaves by running the next program:

/** Name.java by F. M. Carrano
A class of names, each containing a first name and a last name.
*/
public class Name
{
private String first; // First name
private String last; // Last name
/** Creates a name having first and last names.
@param firstName A string giving the name's first name.
@param lastName A string giving the name's last name. */
public Name(String firstName, String lastName)
{
first = firstName;
last = lastName;
} // End constructor
/** @return the entire name as a string. */
public String getName()
{
return first + " " + last;
} // End getName
/** @return the entire name as a string. */
public String toString()
{
return getName();
} // End toString
/** Tests whether this name is equal to another name.
@param other Another Name object.
@return true if the names are equal, otherwise false. */
public boolean equals(Object other)
{
boolean result = false;
if (other instanceof Name)
{
Name otherName = (Name)other;
result = first.equals(otherName.first) &&
last.equals(otherName.last);
} // End if
return result;
} // End equals
public static void main(String[] args)
{
Name jamie1 = new Name("Jamie", "Java");
Name jamie2 = new Name("Jamie", "Java");
Name jamie3 = jamie1;
Object jamie4 = jamie1;
Object jamie5 = new Name("Jamie", "Java");
Name montana = new Name("Montana", "Oakely");
System.out.println("jamie1's name is " + jamie1);
System.out.println("jamie2's name is " + jamie2);
System.out.println("jamie3's name is " + jamie3);
System.out.println("jamie4's name is " + jamie4);
System.out.println("jamie5's name is " + jamie5);
System.out.println("montana's name is " + montana);
System.out.println();
System.out.println("According to the equals method:");
if (jamie1.equals(montana))
System.out.println("  jamie1 and montana are equal.");
else
System.out.println("  jamie1 and montana are not equal.");
if (jamie1.equals(jamie2))
System.out.println("  jamie1 and jamie2 are equal.");
else
System.out.println("  jamie1 and jamie2 are not equal.");
if (jamie1.equals(jamie1))
System.out.println("  jamie1 and jamie1 are equal.");
else
System.out.println("  jamie1 and jamie1 are not equal.");
if (jamie1.equals(jamie3))
System.out.println("  jamie1 and jamie3 are equal.");
else
System.out.println("  jamie1 and jamie3 are not equal.");
if (jamie1.equals(jamie4))
System.out.println("  jamie1 and jamie4 are equal.");
else
System.out.println("  jamie1 and jamie4 are not equal.");
if (jamie1.equals(jamie5))
System.out.println("  jamie1 and jamie5 are equal.");
else
System.out.println("  jamie1 and jamie5 are not equal.");
if (jamie1.equals("Jamie Jones"))
System.out.println("  jamie1 and the string \"Jamie Jones\" are equal.");
else
System.out.println("  jamie1 and the string \"Jamie Jones\" are not equal.");
} // End main
} // End Name

Name’s equals method now behaves well regardless of the data type of its argument.

Free Resources