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
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

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
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

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:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

   public boolean equals(Object obj)
   {
      return this == obj;
   }

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

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
boolean equals(Object obj)
Compares the current object and the passed object
int hashCode()
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
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

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
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

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.

Important:
Regardless of which class overrides the 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
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


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
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


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
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


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
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


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
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


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:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


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.

Important!

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.