liveBook · Manning

Equality by value

As explained in 100 Java Mistakes and How to Avoid Them

The equals method is the Object method conventionally overridden to determine logical equality between objects.

Equality by value asks whether two objects are logically interchangeable, rather than whether two references designate the same object. In Java, == compares references; equals() is the conventional operation for logical equality. Every object is equal to itself, but a well-defined equals() may also treat distinct instances as equal. section 7.1

Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);    #1

Autoboxing can make reference equality appear to work: two Integer references for a cached small value may designate the same object. That coincidence is not a value-comparison rule. section 7.1

Do not assume equals() always compares contents. Its inherited Object implementation is identity-based, so separate arrays, StringBuilder instances, atomic variables, references, and some keys can remain unequal despite similar payloads. Select the comparison API that matches the type: Arrays.equals() or Arrays.deepEquals() for arrays, compareTo() for StringBuilder content, and get() when comparing atomic payloads after updates have finished. section 7.2 section 7.15

Use value equality defensively:

  • Compare primitive wrappers with equals(), not ==; caching can conceal the error until values or construction paths change. section 7.1
  • Check that the argument has the intended type. equals(Object) accepts unrelated objects and normally returns false without a compiler error. section 7.5
  • When defining equality, compare the corresponding fields, include every semantically relevant field, and prefer IDE generation or records where suitable. section 7.6 section 7.15
  • Keep hashCode() consistent with equals(). Equal objects must yield compatible hashes, especially when equality uses unordered or case-insensitive rules. section 7.8
  • Avoid mutating objects after using them as HashMap keys or HashSet elements; changed equality or hashing state can make entries unreachable. section 8.6

A correct call can still be the wrong comparison

"anonymous".equals(name) safely handles a null String value, but it silently returns false if name was accidentally changed from String to String[]. Because equals accepts Object, a refactoring from a single value to a collection or array can preserve compilation while disabling a condition. Treat unexpected false results as possible type mismatches, not only unequal values. section 7.5

Equality is consequential because collections and algorithms use it implicitly. A malformed implementation may quietly lose entries, deduplicate distinct objects, or make HashMap and HashSet lookups fail. Tests should include pairs that differ in each individual equality field and cases exercising the matching hashCode() behavior. section 7.0 section 7.6 section 7.8

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains call-site traps, type-specific alternatives, implementation mistakes, and hashCode consistency rather than deriving the full equality contract.
Examples
Uses cached Integer values, atomic variables, accidental unrelated arguments, faulty field comparisons, and mutated collection keys.
What it assumes
Assumes Java object references and method calls while distinguishing them from primitive-value comparison.
What you can do after
Diagnose/debug with it by choosing appropriate value comparisons and spotting equality failures in collection behavior.
How it connects
Connects equals() with ==, hashCode(), HashMap, HashSet, Arrays.equals(), compareTo(), Java records, and mutable keys.

Also explained in

Compare how 3 books explain equality by value →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.