How do you correctly compare and hash float, double, and array fields inside equals() and hashCode()?
answer
- float/double: Float.compare / Double.compare, not ==
- NaN==NaN is false (breaks reflexivity); +0.0==-0.0 true but different bits
- arrays: Arrays.equals / Arrays.hashCode (not == or inherited)
- nested/object arrays: Arrays.deepEquals / deepHashCode
- records still use identity equals/hashCode for array components
basics
~10 sFor float/double use Float.compare/Double.compare (or Float.hashCode/Double.hashCode), not ==, because of NaN and -0.0. For arrays use Arrays.equals and Arrays.hashCode, not == or the array's own methods, which only check identity.
solid answer
~40 sFloating-point and arrays are the two field types where the naive operators give wrong answers. For float and double in equals, use Float.compare(a,b)==0 / Double.compare(a,b)==0 rather than ==. Two reasons: NaN == NaN is false, but two NaN values should be considered equal for collection consistency, and +0.0 == -0.0 is true, but they have different bit patterns and Double.compare distinguishes them — using compare keeps equals consistent with hashCode (which is bit-based via Double.hashCode). For hashCode, use Float.hashCode/Double.hashCode (internally floatToIntBits/doubleToLongBits). For arrays, the array's own equals and hashCode are identity-based (inherited from Object), so int[]{1,2}.equals(int[]{1,2}) is false. Use Arrays.equals and Arrays.hashCode for one-dimensional arrays, and Arrays.deepEquals/Arrays.deepHashCode for nested or object arrays. Getting these wrong silently breaks HashSet/HashMap membership for objects holding such fields.
code
java · 13 lines@Override public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Measurement m = (Measurement) obj;
return Double.compare(value, m.value) == 0 // NaN/-0.0 safe
&& Arrays.equals(samples, m.samples); // content, not identity
}
@Override public int hashCode() {
int result = Double.hashCode(value);
result = 31 * result + Arrays.hashCode(samples);
return result;
}go deeper
Knows arrays need Arrays.equals/Arrays.hashCode and floating-point needs Double.compare/Double.hashCode rather than == and the inherited methods.
Can apply the right helper per field type and explain that array equals/hashCode are identity-based by default.
Explains the NaN reflexivity and signed-zero consistency reasons for Double.compare, deepEquals for nested arrays, and the equals/hashCode consistency that ties them together.
Anticipates the record-with-array-component pitfall, sets conventions to avoid array fields in value types or mandate overrides, and reviews for content-vs-identity hashCode mismatches across hash-keyed domain types.
## The two trap field types Most fields compare fine with `==` (primitives) or `Objects.equals` (objects). Two categories need special care: **floating-point** (`float`, `double`) and **arrays**. Mishandling them produces an equals/hashCode pair that compiles, looks right, and silently corrupts hash-based collections. ## Floating point in equals() Use `Float.compare(a, b) == 0` / `Double.compare(a, b) == 0`, **not** `==`. Two distinct issues: 1. **NaN (Not-a-Number).** By IEEE-754 rules, `Double.NaN == Double.NaN` is `false`. So with `==`, an object whose field is NaN would not even equal *itself* via that field — violating **reflexivity**. `Double.compare(NaN, NaN)` returns 0 (treats them as equal), restoring reflexivity. 2. **Signed zero.** `+0.0 == -0.0` is `true`, but they have different bit patterns. `Double.compare(+0.0, -0.0)` returns a non-zero value (treats them as different). Why does that matter? Because `Double.hashCode(+0.0) != Double.hashCode(-0.0)` (hashCode is bit-based). If equals said they were equal (via `==`) but hashCode said they differ, you would **break the equal-objects-equal-hashCodes contract**. Using `Double.compare` in equals keeps equals and hashCode consistent. ```java // equals Double.compare(this.value, other.value) == 0 // hashCode Double.hashCode(value) // == doubleToLongBits folded to int ``` ## Arrays in equals() and hashCode() An array is an object whose `equals`/`hashCode` are **inherited from `Object`** — i.e. **identity-based**. So: ```java int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; a.equals(b); // false — different objects a.hashCode(); // identity hash, unrelated to contents ``` Use the `java.util.Arrays` helpers, which compare/hash **by element**: - One-dimensional: `Arrays.equals(a, b)` and `Arrays.hashCode(a)`. - Nested arrays or arrays of objects (multidimensional): `Arrays.deepEquals(a, b)` and `Arrays.deepHashCode(a)` (these recurse). ```java // equals Arrays.equals(this.tags, other.tags) // hashCode contribution 31 * result + Arrays.hashCode(tags) ``` ## Consistency is the whole point The rule that ties it together: **equals and hashCode must agree**. If equals treats two field values as equal, hashCode must produce the same contribution for them. `Float.compare`/`Double.compare` pair correctly with `Float.hashCode`/`Double.hashCode`; `Arrays.equals` pairs with `Arrays.hashCode`. Mixing a content-based equals with an identity-based hashCode (the classic array mistake) means an object you stored under a key can never be found again, because it lands in a bucket computed from the wrong hash. ## What records do A Java `record` generates equals/hashCode that already use `Float`/`Double` value semantics and component-wise comparison — **but for array components it still uses the array's identity-based equals/hashCode**, so records with array fields share this hazard. If a record holds an array, you typically override equals/hashCode (and toString) or avoid array components.
- Why does using == for a double field break the equals contract when the value can be NaN?NaN == NaN evaluates to false, so an object whose double field is NaN would not equal itself through that comparison, violating reflexivity (x.equals(x) must be true). Double.compare(NaN, NaN) returns 0, preserving reflexivity.
- A record has an int[] component. Do its generated equals/hashCode compare the array contents?No. The generated methods use the component's own equals/hashCode, which for arrays is identity-based. Two records with equal-content but distinct arrays compare unequal and hash differently. You must override the methods (using Arrays.equals/hashCode) or avoid array components.
saying these in an interview costs you the question
- Comparing double fields with == and assuming it is fine
- Using array.equals(other) or array.hashCode() (identity-based) for content comparison
- Content-based equals paired with identity-based array hashCode — object becomes unfindable in a HashSet
- Assuming a record with an array component compares array contents (it does not)
- Thinking NaN handling is irrelevant for equals — it breaks reflexivity