Methods Common to All Objects

Methods Common to All Objects

Obey the General Contract When Overriding equals

Overriding equals looks simple: check whether two objects match. In production systems, mistakes inside equals break collections, caches, and risk logic silently.

Java defines a strict contract.

An equals implementation must be:

  • Reflexive x.equals(x) returns true.
  • Symmetric x.equals(y) matches y.equals(x).
  • Transitive If x.equals(y) and y.equals(z), then x.equals(z).
  • Consistent Repeated calls return the same result while state stays unchanged.
  • Non-null x.equals(null) returns false.

Violating one rule leads to undefined behavior in List, Set, and Map.

The Symmetry Trap

Engineers often try to add “friendly” interoperability.

Example:
public final class CaseInsensitiveString {
    private final String s;

    @Override
    public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        if (o instanceof String)
            return s.equalsIgnoreCase((String) o);
        return false;
    }
}        
Usage:
cis.equals("Hindi")      // true
"Hindi".equals(cis)     // false        

Symmetry breaks.

Collections rely on symmetry. list.contains(cis) may return false even when the object exists.

Example

A trade ID wrapper compared directly with String breaks lookup inside reconciliation sets. Trades appear missing during end-of-day checks while sitting in memory.


The Transitivity Trap: ColorPoint

Base class:
class Point {
    final int x, y;
}        
Extended class:
class ColorPoint extends Point {
    final Color color;
}        

Questions appear:

  • Should Point(1,2) equal ColorPoint(1,2,RED)?
  • Should color participate in equality?

Any choice violates either symmetry or transitivity once objects mix.

Java best practice's conclusion:

There is no safe way to extend an instantiable class and add a value field while preserving the equals contract.

The Fix: Composition Over Inheritance

Replace:
class ColorPoint extends Point        
With:
class ColorPoint {
    private final Point point;
    private final Color color;
}        

Now equality becomes,

  • Compare point
  • Compare color

No paradox. No mixed-type logic.

Example

Position objects extended through inheritance break equality inside portfolio aggregation. Composition keeps equality stable across pricing, margin, and reporting pipelines.


Always Override hashCode When Overriding equals

Rule:

If a.equals(b) returns true, then a.hashCode() must match b.hashCode().

Failure effect:

  • Object stored in HashMap
  • Lookup uses a different bucket
  • Retrieval returns null

The object exists, yet stays unreachable.

Example

Risk caches keyed by instrument objects fail during lookup. Margin logic reports missing exposure while data remains allocated in memory.


Practical Rules

  • Compare only same-class types in equals
  • Avoid cross-type equality
  • Prefer composition for value extension
  • Generate hashCode from the same fields used in equals
  • Test equality inside HashSet and HashMap


Takeaway

equals defines identity. Collections trust it blindly.

A small shortcut inside equals produces large system failures in trading, settlement, pricing, and reporting paths.

Correctness in equality protects correctness everywhere else.

To view or add a comment, sign in

More articles by Ankur Mistry

  • Lambdas, Method References, Streams

    Prefer Lambdas to Anonymous Classes Before Java 8: With lambdas: Shorter. Clearer.

  • Generics in Java

    Generics replace runtime failure with compile time guarantees. Before Java 5, casts hid defects until production…

  • Prefer Interfaces to Abstract Classes

    Java supports single inheritance. Extending an abstract class consumes the only superclass slot.

  • Inheritance vs Composition in Java

    Object reuse looks simple: extend a class and gain behavior. In production systems, inheritance creates long term risk…

  • Control Access to Protect Design

    Encapsulation drives decoupling. Once access spreads, refactoring cost rises across teams and systems.

  • Avoid Finalizers and Cleaners

    Never rely on object finalization for resource management. In C++, destructors run at deterministic points.

  • Avoid Creating Unnecessary Objects

    This item sounds simple, yet performance loss often starts here. Classic Mistake: Redundant Allocation Better form:…

  • Prefer Dependency Injection to Hardwiring Resources

    A design rule many engineers follow instinctively, yet often violate under time pressure. Problem: Hardwired…

  • Enforce Noninstantiability

    Some classes exist only as containers for static methods and constants. Examples from the JDK: These classes represent…

  • Singleton Instance

    Enforcing a Single Instance with the Singleton Property After covering factories and builders, object creation control…

Explore content categories