# Mastering Object Equality in Java

## Introduction

The `==` operator is used to compare references, while the `equals` method is used to compare the contents of objects.

## Comparison for predefined types

### Primitive types

The comparison operator works with predefined types, such as integers, booleans, and characters:

```java
int num = 2;
if (num == 2) {
    System.out.println("num equals two");
}
```

```java
boolean gameIsOver = true;
if (gameIsOver) {  // Equivalent to: if (gameIsOver == true)
    System.out.println("The game is over");
}
```

### Strings

However, there is a widely used predefined type that does not follow these rules: the `String` type.

```java
String station = "SUMMER";
if (station.toLowerCase() == "summer") {  // INCORRECT!!
    System.out.println("It's summer");
}
```

This does not display the expected message.  
The reason is that the `==` operator, when applied to strings, compares references, that is, memory locations. In principle, each variable is at a different memory location.

To compare the contents of strings, you should use the `equals` method defined in Java:

```java
if (station.toLowerCase().equals("summer")) {
    System.out.println("It's summer");
}
```

## How to compare objects

For objects of user-defined classes, you also use the `equals` method:

```java
class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getters and setters
}
```

```java
Person person1 = new Person("Sara", 23);
Person person2 = new Person("Sara", 23);
if (person1.equals(person2)) {
    System.out.println("person1 and person2 are the same person");
}
```

The message is not displayed, so you can deduce that they are considered different people. This is because, by default, the equals method in the Object class only compares references, not the content of the fields.

The solution is to compare all the relevant fields:

```java
if (person1.getName().equals(person2.getName()) && person1.getAge() == person2.getAge()) {
    System.out.println("person1 and person2 are the same person");
}
```

To avoid repeating these conditions every time, you can define the `equals()` method:

```java
public boolean equals(Person person) {
    if (person.getName().equals(this.getName()) && person.getAge() == this.getAge()) {
        return true;
    } else {
        return false;
    }
}
```

### Overriding `equals()`

All classes created in Java inherit methods from the `Object` class. One of these methods is:

```java
public boolean equals(Object object);
```

By default, `equals()` in `Object` checks if two references point to the same object (i.e., using `==`). Overriding `equals()` allows you to define equality based on the object's content:

```java
@Override
public boolean equals(Object object) {
    // Explicit cast from Object to the type we're interested in
    Person person = (Person) object;
    return person.getName().equals(this.getName()) && person.getAge() == this.getAge()) {
}
```

The `equals` method must return false when one of the objects is null:

```java
@Override
public boolean equals(Object object) {
    if (object == null) {
        return false;
    }
    // ...
}
```

## Using `instanceof`

You can use `instanceof` to check the type before casting:

```java
@Override
public boolean equals(Object object) {
    if (!(object instanceof Person)) {
        return false;
    }
    Person person = (Person) object;
    // ...
}
```

The null check is unnecessary because the `instanceof` operator returns false if its first operand is null.

## Using `Objects.equals()`

When comparing object fields, the previous approach is **not null-safe**. That means the condition will throw a `NullPointerException` if a field is null.  
To avoid this, we use the `Objects.equals()` method, which returns false if either object is null and true if both are null:

```java
@Override
public boolean equals(Object object) {
    if (!(object instanceof Person)) return false;
    Person person = (Person) object;
    return Objects.equals(name, person.name) && age == person.age;
}
```

The disadvantage of using `Objects.equals()` is that it is not type-safe, so type checks are still necessary.

## Preserve the equality when subclassing

The equals() method generated by many IDEs uses **getClass()** instead of **instanceof**.  
When you use **getClass()**, you are performing a strict comparison. This means that derived classes or subclasses cannot be compared-they will not be considered equal to the parent class. If you want to compare subclasses with the parent class, you need to use the **instanceof** operator.

Some authors prefer using **instanceof** to allow subclasses to be compared, because inheritance is one of the fundamental properties of object-oriented programming. Additionally, symmetry is maintained, since `this.equals(employee)` gives the same result as `employee.equals(this)`

Let's take a look at an example when comparing two objects of the same class hierarchy.

```java
class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        // Check for reference equality
        if (this == obj)
            return true;

        // if (obj == null || getClass() != obj.getClass())
        // return false;
        if (!(obj instanceof Person)) {
            return false;
        }

        // Field comparison
        Person other = (Person) obj;
        return getAge() == other.getAge() && Objects.equals(getName(), other.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

}
```

```java
class Employee extends Person {

    public Employee(String name, int age) {
        super(name, age);
    }

    @Override
    public boolean equals(Object obj) {
        // Check for reference equality
        if (this == obj)
            return true;

        // if (obj == null || getClass() != obj.getClass())
        if (!(obj instanceof Person))
            return false;

        // Field comparison
        Person other = (Person) obj;
        return getAge() == other.getAge() && Objects.equals(getName(), other.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
```

```java
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;

public class SubclassComparison {
    public static void main(String[] args) {
        // List of objects of the parent class
        List<Person> personsList = new ArrayList<>();
        Person alice = new Person("Alice", 30);
        Person bob = new Person("Bob", 25);
        personsList.add(alice);
        personsList.add(bob);

        // List of objects of the subclass
        List<Employee> employeesList = new ArrayList<>();
        Employee aliceEmployee = new Employee("Alice", 30);
        Employee bobEmployee = new Employee("Bob", 25);
        employeesList.add(aliceEmployee);
        employeesList.add(bobEmployee);

        // Compare the lists
        boolean areEqual = personsList.equals(employeesList);
        System.out.println("Employee lists are equal: " + areEqual); 
    }
}
```

## HashCode

The **hashCode()** method is used to generate a hash code for an object.  
The hash code is a unique value that represents the object in hash-based collections.

Overriding **hashCode()** is crucial because it ensures that objects behave correctly when used in hash-based collections like *HashMap*, *HashSet*, and *Hashtable*.

## Records

If you use a **record**, introduced in Java 16, the compiler implicitly creates the constructor, getters, **equals()**, **hashCode()**, and toString() methods.

```java
record Person(String name, int age) { }
```

## Conclusion

Understanding the difference between the == operator and the equals() method is essential.

The == operator compares object references, while equals() should be overridden to compare the actual content of objects.

When overriding equals(), **always override hashCode()** as well to ensure correct behavior in hash-based collections.

Using **instanceof** in your equals() implementation allows for greater flexibility and supports inheritance, which is a core principle of object-oriented programming.

For modern Java applications, **records** offer a concise way to create immutable data classes with proper equality and hashing behavior generated automatically by the compiler.

