equals and hashCode Contract

== vs equals

== compares references (same object in memory?). equals() compares logical value. Object.equals by default is just ==, so value classes must override it.

Example

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        String a = new String("upi"), b = new String("upi");
        System.out.println("a == b:      " + (a == b));
        System.out.println("a.equals(b): " + a.equals(b));

        Money m1 = new Money(100, "INR"), m2 = new Money(100, "INR");
        System.out.println("m1.equals(m2): " + m1.equals(m2));
        System.out.println("same hashCode: " + (m1.hashCode() == m2.hashCode()));
    }
}

final class Money {
    private final long amount;
    private final String currency;
    Money(long amount, String currency) { this.amount = amount; this.currency = currency; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money other)) return false;
        return amount == other.amount && currency.equals(other.currency);
    }

    @Override
    public int hashCode() { return Objects.hash(amount, currency); }
}

Output

a == b:      false
a.equals(b): true
m1.equals(m2): true
same hashCode: true

The Contract

RuleMeaning
Reflexivex.equals(x) is true
Symmetricx.equals(y) ⇔ y.equals(x)
Transitivex = y and y = z ⇒ x = z
Consistentsame answer every time if nothing changes
Nullx.equals(null) is false
hashCodeequal objects must have equal hash codes (unequal objects may collide)

Tip: Records generate correct equals, hashCode and toString for you. IDEs (IntelliJ: Alt+Insert) and Lombok @EqualsAndHashCode can generate them for classes.