Understand object identity, value equality, readable toString output, and the equals/hashCode contract through ten Java SE 25 interview questions.
Answer: Declare @Override public String toString() and return a non-null, readable description of the object. Include the state that helps a reader understand it. The inherited representation looks like a class name followed by @ and a hexadecimal hash code; that suffix is not a guaranteed memory address.
For example, a student object might return "Student[lastName=" + lastName + "]". Treat this as display or diagnostic text unless your class explicitly promises a stable format. Records supply a component-based representation automatically.
Answer: For reference operands, == tests whether both refer to the same object, or both are null. equals() is an instance method whose behavior depends on the class: String compares text, while Object compares identity. Primitive equality compares values after the applicable conversions.
Two separately constructed strings can be equal without being identical. When primitive and wrapper operands are mixed, unboxing can occur, so first identify the operand types. Do not use wrapper identity to test numeric equality.
Answer: Use it when you want the equality defined by the object type, such as matching two names or two value objects. Use Objects.equals(a, b) when either reference can be null. Calling a.equals(b) throws NullPointerException if a is null.
Arrays retain identity-based equals; use the appropriate Arrays.equals overload for element comparisons or Arrays.deepEquals for nested object arrays. An equals call does not automatically mean that every field is compared.
Answer: Its equality and hash code must agree, and equality-relevant state must stay stable while the key is stored. Inherited Object behavior is valid when identity is the intended key semantics. If you define value equality, normally override both equals(Object) and hashCode().
HashMap is a general-purpose map implementation. The older Hashtable has synchronized operations and rejects null keys and values. These implementation differences do not remove the equality/hash contract.
Answer: It uses identity: for a non-null receiver, the result is true exactly when the argument is the same object. It does not inspect fields. Subclasses may override this behavior, so a call through an Object-typed reference can still dispatch to String.equals or another override.
Answer: Override it when independently created instances should represent the same value. Define which state determines equality, then make the relation reflexive, symmetric, transitive, and consistent while that state is unchanged; comparison with null must be false.
A final value class or an appropriate record keeps the design manageable. Inheritance can break symmetry when subclasses add equality-relevant state. Always review the corresponding hashCode implementation and avoid changing key state inside a hash collection.
Answer: They are immutable and provide value-based equality with matching hash codes. Their key state cannot change after insertion. Choose a type whose equality matches the application: an Integer containing 1 is not equal to a Long containing 1, even though the represented numbers are mathematically equal.
Answer: Check the argument type, then compare the field values with Objects.equals. Provide a hashCode based on the same value. A class can access the private fields of another instance of that same class.
This exercise deliberately defines equality by last name. Real student identity usually needs a unique identifier because different people can share a surname. The example permits null surnames and handles them consistently.
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class StudentKeyExample {
static final class Student {
private final String lastName;
Student(String lastName) { this.lastName = lastName; }
@Override public boolean equals(Object other) {
return other instanceof Student student
&& Objects.equals(lastName, student.lastName);
}
@Override public int hashCode() {
return Objects.hashCode(lastName);
}
@Override public String toString() {
return "Student[lastName=" + lastName + "]";
}
}
public static void main(String[] args) {
Student a = new Student("Hauck");
Student b = new Student("Hauck");
Map<Student, String> notes = new HashMap<>();
notes.put(a, "Registered");
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(notes.get(b));
System.out.println(a);
System.out.println(new Student(null).equals(new Student(null)));
}
}
Answer: No. A permitted instanceof test returns false for an incompatible value or null. A separate checked cast of an incompatible, non-null reference throws ClassCastException. Casting null to a reference type produces null.
Pattern matching, as used in the preceding example, introduces a correctly typed variable only when the test succeeds. No separate cast is necessary inside the successful branch. Some impossible type tests and casts are rejected by the compiler before execution.
Answer: They are public boolean equals(Object obj), public int hashCode(), and public String toString(). They are instance methods, not static class methods. They are inherited or overridden throughout the class hierarchy, including by objects whose runtime type is Class<?>. Object has other public methods too; these three are the focus of this page.
Java SE 25 references: Object, Objects, HashMap, Arrays.