🎓 A1Learner
🔍
0 of 40 lessons completed

Java equals and hashCode

equals compares two objects for equality, and hashCode gives an object an int code for hash-based collections. The default equals rule compares identity, not field values.

Default equals prints false for field-twins

The two students hold the same name and score in separate objects. The default rule compares identity, so the twin answers false.

public class Main {
  public static void main(String[] args) {
    Student a = new Student("Ana", 92);
    Student b = new Student("Ana", 92);
    System.out.println(a.equals(b));
    System.out.println(a.equals(a));
  }
}
class Student {
  String name;
  int score;
  Student(String name, int score) {
    this.name = name;
    this.score = score;
  }
}

An overridden equals compares the fields

The equals method below compares name and score. The twin now answers true, and a changed score answers false.

public class Main {
  public static void main(String[] args) {
    Student a = new Student("Ana", 92);
    Student b = new Student("Ana", 92);
    Student c = new Student("Ana", 80);
    System.out.println(a.equals(b));
    System.out.println(a.equals(c));
  }
}
class Student {
  String name;
  int score;
  Student(String name, int score) {
    this.name = name;
    this.score = score;
  }
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null || getClass() != other.getClass()) {
      return false;
    }
    Student student = (Student) other;
    return score == student.score && name.equals(student.name);
  }
}

Equal objects hash equal

Objects.hash builds one code from the given fields. The boolean line compares the two codes instead of printing them.

import java.util.Objects;

public class Main {
  public static void main(String[] args) {
    Student a = new Student("Ana", 92);
    Student b = new Student("Ana", 92);
    System.out.println(a.equals(b));
    System.out.println(a.hashCode() == b.hashCode());
  }
}
class Student {
  String name;
  int score;
  Student(String name, int score) {
    this.name = name;
    this.score = score;
  }
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null || getClass() != other.getClass()) {
      return false;
    }
    Student student = (Student) other;
    return score == student.score && name.equals(student.name);
  }
  public int hashCode() {
    return Objects.hash(name, score);
  }
}

A twin key is found only with both methods

BareStudent overrides neither method, and its twin lookup prints null. FullStudent overrides both, and its twin lookup finds the stored seat.

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Main {
  public static void main(String[] args) {
    Map<BareStudent, String> bare = new HashMap<>();
    bare.put(new BareStudent("Ana", 92), "Seat 4");
    System.out.println(bare.get(new BareStudent("Ana", 92)));
    Map<FullStudent, String> full = new HashMap<>();
    full.put(new FullStudent("Ana", 92), "Seat 4");
    System.out.println(full.get(new FullStudent("Ana", 92)));
  }
}
class BareStudent {
  String name;
  int score;
  BareStudent(String name, int score) {
    this.name = name;
    this.score = score;
  }
}
class FullStudent {
  String name;
  int score;
  FullStudent(String name, int score) {
    this.name = name;
    this.score = score;
  }
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null || getClass() != other.getClass()) {
      return false;
    }
    FullStudent student = (FullStudent) other;
    return score == student.score && name.equals(student.name);
  }
  public int hashCode() {
    return Objects.hash(name, score);
  }
}

Example

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Main {
  public static void main(String[] args) {
    Map<Student, String> rooms = new HashMap<>();
    rooms.put(new Student("Ana", 92), "Room 1");
    rooms.put(new Student("Ben", 75), "Room 2");
    Student twin = new Student("Ana", 92);
    System.out.println(twin.equals(new Student("Ana", 92)));
    System.out.println(twin.hashCode() == new Student("Ana", 92).hashCode());
    System.out.println(rooms.get(twin));
  }
}
class Student {
  String name;
  int score;
  Student(String name, int score) {
    this.name = name;
    this.score = score;
  }
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null || getClass() != other.getClass()) {
      return false;
    }
    Student student = (Student) other;
    return score == student.score && name.equals(student.name);
  }
  public int hashCode() {
    return Objects.hash(name, score);
  }
}

Key points

  • Identity means the same object in memory, not a second object with matching fields.
  • An overridden equals can compare the fields of two objects instead.
  • Equal objects must produce equal hash codes.
  • A hash map lookup uses hashCode to narrow the search and equals to confirm the key.
  • A twin key is found only when both equals and hashCode follow the field rule.

In this example, Student overrides equals and hashCode from its two fields. A fresh twin object compares equal and hashes equal to the stored key. The map lookup with that twin answers Room 1.

Note

Overriding one of the two methods without the other breaks the pair.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When does a HashMap find a value stored under a twin object key?