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
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
. -
Transitive: For 3 non-null objects
x
,y
andz
, ifx.equals(y)
istrue
andy.equals(z)
istrue
thenx.equals(z)
should returntrue
. -
Consistent: For 2 non-null objects
x
andy
, 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
if
statement above covers the reflective case. -
The second
if
statement covers the not equal to null case. -
The third
if
statement checks if the input object is of the same type, before we do final comparisons. -
The last lines is where, after casting the input
Object
into ourDictionaryEntry
class, we start comparing the state of the objects to make a decision on equality. In our case, we check theword
field (via theObjects.equals
static 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.