As covered in the inheritance sections, we know that the base class of all classes in Java is the Object class.
One of the inherited methods from the Object class is the equals method that is used to check if the object is equal to an object provided as an input and has the following signature:
public boolean equals(Object obj);
This method should return true if this object is equal to the object provided as a parameter, or false otherwise.
| It’s important to note that we’re talking about object equality here and not of object reference equality which was covered in the section about Equality and Relational Operators in Chapter 2. |
The equals contract
The documentation of the equals method in the Object class defines a contract that should be followed by any classes that override it.
In summary, the contract specifies 5 main points, the first 3 being the same as the mathematical properties for equality:
-
Reflective: For a non-null object
x,x.equals(x)should returntrue(an object should be equal to itself). -
Symmetric: For 2 non-null objects
xandy,x.equals(y)should returntrueif and only ify.equals(x)returnstrue. -
Transitive: For 3 non-null objects
x,yandz, ifx.equals(y)istrueandy.equals(z)istruethenx.equals(z)should returntrue. -
Consistent: For 2 non-null objects
xandy, assuming their state hasn’t changed, multiple invocations ofx.equals(y)should always return the same value. -
Not equal to null: For a non-null object
x,x.equals(null)should returnfalse.
The documentation also talks about the relationship with the hashCode method which will be covered in the next section.
|
equals example
For our example, let’s assume we are using a modified version of the DictionaryEntry class:
public class DictionaryEntry {
private final String word;
private final String definition;
public DictionaryEntry(String word, String definition) {
this.word = word;
this.definition = definition;
}
public final void printEntry() {
System.out.println(word + ": " + definition);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof DictionaryEntry)) {
return false;
}
DictionaryEntry other = (DictionaryEntry) obj;
return Objects.equals(word, other.word);
}
}
You’ll notice that our equals method only uses the word field and not the definition as part of the equality checks. The decision on what makes two objects equal depends on the application you’re building, for our example we’ve decided that two DictionaryEntry objects are equal if they represent the same word.
Inside our equals method we are doing the following:
-
The first
ifstatement above covers the reflective case. -
The second
ifstatement covers the not equal to null case. -
The third
ifstatement checks if the input object is of the same type, before we do final comparisons. -
The last lines is where, after casting the input
Objectinto ourDictionaryEntryclass, we start comparing the state of the objects to make a decision on equality. In our case, we check thewordfield (via theObjects.equalsstatic method provided by Java).
Most IDEs nowadays provide a way to auto-generate an equals method that follows the required contract.
|
The Objects class provide a set of static utility methods to do common operations on objects.
|
Let’s give it a try:
In the example code run the JavaObjectEqualityPropertiesApp
|
DictionaryEntry run1 = new DictionaryEntry("run", "definition of run");
System.out.println("Reflective property:");
System.out.println("run1 equals itself: " + run1.equals(run1));
DictionaryEntry run2 = new DictionaryEntry("run", "definition of run");
System.out.println("Symmetric property:");
System.out.println("run1 equals run2: " + run1.equals(run2));
System.out.println("run2 equals run1: " + run2.equals(run1));
DictionaryEntry run3 = new DictionaryEntry("run", "definition of run");
System.out.println("Transitive property:");
System.out.println("run1 equals run2: " + run1.equals(run2));
System.out.println("run2 equals run3: " + run2.equals(run3));
System.out.println("run1 equals run3: " + run1.equals(run3));
System.out.println("Not equal to null:");
System.out.println("run1 equals null: " + run1.equals(null));
DictionaryEntry stop = new DictionaryEntry("stop", "definition of stop");
System.out.println("Not equal to different DictionaryEntry: ");
System.out.println("run1 equals stop: " + run1.equals(stop));
Output:
Reflective property:
run1 equals itself: true
Symmetric property:
run1 equals run2: true
run2 equals run1: true
Transitive property:
run1 equals run2: true
run2 equals run3: true
run1 equals run3: true
Not equal to null:
run1 equals null: false
Not equal to different DictionaryEntry:
run1 equals stop: false
Example explained
This is an example of the different properties of the equals method behaving as we expect them to. Here we are using a DictionaryEntry object run1 that represents the word "run" and we’re creating two additional objects run2 and run3 that also represent the same word to illustrate the properties of the equals contract.
We have 3 object references run1, run2 and run3 that point to 3 different objects, this is, the 3 references themselves are different, but the objects they point to are equal based on our application needs (in our case, they represent the same word).
We also create an object stop that represents a different word to illustrate that our equals method behaves as we expect it when we compare objects that represent different words.
However, there are common pitfalls when talking about object equality which we’ll cover in the next section.