1. Comparing objects in Java
In Java, objects can be compared both by reference and by value.
Comparing references
If two variables point to the same object in memory, then the references stored in these variables are equal. If you compare these variables using the equality operator (==
), you get true, and that result makes sense. Everything is simple here.
Code | Console output |
---|---|
|
|
Comparing by value
But you can often encounter situations where two variables refer to two distinct objects that are identical. For example, two different strings objects that contain the same text.
To determine whether different objects are identical, use the equals()
method. For example:
Code | Console output |
---|---|
|
|
The equals
method is not limited to the String
class. Every class has it.
Even classes that you write on your own, and here's why.
2. Object
class
All classes in Java inherit the Object
class. Java's creators came up with this approach.
And if a class inherits the Object
class, then it gains all the methods of the Object
class. And this is a major consequence of inheritance.
In other words, every class has the methods of the Object
class, even if their code does not mention them.
These inherited methods include methods related to object comparison. These are the equals()
and hashCode()
methods.
Code | In reality, here's what we'll have: |
---|---|
|
|
In the example above, we created a simple Person
class with name and age parameters, but not a single method. But because all classes inherit the Object
class, the Person
class automatically has two methods:
Method | Description |
---|---|
|
Compares the current object and the passed object |
|
Returns the hashcode of the current object |
It turns out that absolutely every object has the equals
method, and objects of different types can be compared with each other. Such code will compile and work perfectly.
Code | Console output |
---|---|
|
|
|
|
3. equals()
method
The equals()
method, inherited from the Object
class, implements the simplest algorithm for comparing the current object with passed objects: it just compares references to the objects.
You get the same result if you just compare Person
variables instead of calling the equals()
method. Example:
Code | Console output |
---|---|
|
|
When the equals
method is called on a
, it simply compares the reference stored in the a
variable with the reference stored in the b
variable.
However, comparison works differently for the String
class. Why?
Because the folks who created the String
class wrote their own implementation of the equals()
method.
Implementation of the equals()
method
Now let's write our own implementation of the equals method in the Person
class. We'll consider 4 main cases.
equals
method, it always takes an Object
object as an argument
Scenario 1: the same object on which the equals
method is called is also passed to the equals
method. If the references of the current object and the passed object are equal, the method must return true
. An object is equal to itself.
In code it will look like this:
Code | Description |
---|---|
|
Compare references |
Scenario 2: null
is passed to the equals
method — we have nothing to compare to. The object on which the equals
method is called is definitely not null, so we need to return false
in this case.
In code it will look like this:
Code | Description |
---|---|
|
Compare references Is the passed object null ?
|
Scenario 3: a reference to an object that is not a Person
is passed to the equals
method. Is the Person
object equal to the non-Person
object? That is a question for the developer of the Person
class to decide however he or she wants.
But usually objects must be of the same class to be considered equal. Therefore, if something other than an object of the Person
class is passed to our equals method, then we will always return false
. How can you check the type of an object? That's right — by using the instanceof
operator.
Here's what our new code looks like:
Code | Description |
---|---|
|
Compare references Is the passed object null ?
If the passed object is not a Person |
4. Comparing two Person
objects
What did we end up with? If we have reached the end of the method, then we have a Person
object reference that is not null
. So we convert it to a Person
and compare the relevant internal data of both objects. And that is our fourth scenario.
Code | Description |
---|---|
|
Compare references Is the passed object null ?
If the passed object is not a Person
Typecasting |
And how do you compare two Person
objects? They are equal if they have the same name (name
) and age (age
). The final code will look like this:
Code | Description |
---|---|
|
Compare references Is the passed object null ?
If the passed object is not a Person
Typecasting |
But that's not all.
First, the name field is a String
, so you need to compare the name field by calling the equals
method.
this.name.equals(person.name)
Second, the name
field may be null
: in that case, you cannot call equals
on it. You need an additional check for null
:
this.name != null && this.name.equals(person.name)
That said, if the name field is null
in both Person
objects, then the names are still equal.
The code for the fourth scenario might look like this:
|
If the ages are not equal, immediately return false
If this.name is equal to null , there is no point in comparing using the equals method. Here either the second name field is equal to null , or it is not.
Compare the two name fields using the equals method.
|
5. hashCode()
method
In addition to the equals
method, which is intended to perform a detailed comparison of all the fields of both objects, there is another method that can be used for an imprecise but very quick comparison: hashCode()
.
Imagine you are alphabetically sorting a list of thousands of words, and you need to repeatedly compare pairs of words. And the words are long, consisting of lots of letters. Generally speaking, such a comparison would take a very long time.
But it can be accelerated. Suppose we have words that begin with different letters — it's immediately clear that they are different. But if they begin with the same letters, then we can't yet say what the result will be: the words may turn out to be equal or different.
The hashCode()
method works using a similar principle. If you call it on an object, it returns some number — analogous to the first letter in a word. This number has the following properties:
- Identical objects always have the same hashcode
- Different objects can have the same hashcode, or their hashcodes can be different
- If objects have different hashcodes, then the objects are definitely different
To make this even more clear, let's reframe these properties in terms of words:
- Identical words always have the same first letters.
- Different words can have the same first letters, or their first letters can be different
- If words have different first letters, then the words are definitely different
The last property is used to accelerate comparison of objects:
First, the hashcodes of the two objects are calculated. If these hashcodes are different, then the objects are definitely different, and there is no need to compare them further.
But if the hashcodes are the same, then we still have to compare the objects using the equals method.
6. Contracts in code
The behavior described above must be implemented by all classes in Java. During compilation, there is no way to check whether objects are compared correctly.
Java programmers have a universal agreement that if they write their own implementation of the equals() method and thereby override the standard implementation (in the Object
class), they must also write their own implementation of the hashCode()
method in such a way that the aforementioned rules are satisfied.
This arrangement is called a contract.
If you implement only the equals()
or only the hashCode()
method in your class, then you are in gross violation of the contract (you've broken the agreement). Don't do this.
If other programmers use your code, it may not work correctly. What's more, you will use code that relies on adherence to the above contracts.
When searching for an element, all Java collections first compare the hashcodes of objects, and only then perform a comparison using the equals
method.
That means that if you give your own class an equals
method but you do not write your own hashCode()
method or you implement it incorrectly, then collections may not work correctly with your objects.
For example, you might add an object to a list and then search for it using the contains()
method, but the collection might not find your object.
GO TO FULL VERSION