Back to Object Class, equals & hashCode
Q7Question 7 of 9

How do you compare two values safely when either may be null?

Key Concepts
  • Objects.equals(a, b): null-safe, returns true if both null, false if one is null
  • "constant".equals(variable): null-safe because the constant is never null
  • Optional.ofNullable(x).orElse(default): convert null to a default value before comparing
  • Avoid a.equals(b) when a might be null – throws NullPointerException
  • Understanding this concept is essential for Java interviews at senior level

Definition

Use Objects.equals(a, b) for null-safe comparison. It handles null on both sides without throwing NullPointerException.

Three null-safe comparison techniques:

DIAGRAM
- **Objects.equals(a, b):** Handles all cases. `null, null → true`. `null, non-null → false`. `non-null, non-null → a.equals(b)`.
- **"literal".equals(variable):** Put the known non-null value on the left.
- **Optional:** `Optional.ofNullable(x).map(...).orElse(false)` for complex comparisons.

When comparing two bank forms, always check if either form is missing (null) before reading its contents. Objects.equals is the clerk who handles missing forms gracefully instead of crashing.

Understanding the Concepts

Objects.equals

JAVA
import java.util.Objects;

String a = null;
String b = "SAV2001";

Objects.equals(a, b);       // false (no NPE)
Objects.equals(null, null);  // true
Objects.equals(a, a);        // true

// Without Objects.equals:
// a.equals(b);              // NPE! a is null

"literal".equals(variable)

JAVA
// SAFE: literal is never null
if ("SAVINGS".equals(accountType)) { ... }

// UNSAFE: accountType might be null
// if (accountType.equals("SAVINGS")) { ... }  // NPE if null

In equals() implementations

JAVA
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Account other)) return false;
    return Objects.equals(accountId, other.accountId)  // null-safe
        && Objects.equals(balance, other.balance);      // null-safe
}

What are common mistakes?

A common mistake is overriding equals() without overriding hashCode(), which breaks HashMap and HashSet behavior.

Wrong approach:

JAVA
package com.bank.model;

public class Account {
    private String accountId;

    // WRONG: overrides equals but not hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        return accountId.equals(((Account) o).accountId);
    }
    // Missing hashCode override!
}
JAVA
// This breaks HashMap lookups
Map<Account, String> map = new HashMap<>();
Account a1 = new Account("SAV2001");
map.put(a1, "Rajesh");
Account a2 = new Account("SAV2001");
System.out.println(map.get(a2));  // null! Different hashCode

Correct approach:

JAVA
package com.bank.model;

import java.util.Objects;

public class Account {
    private String accountId;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        return accountId.equals(((Account) o).accountId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(accountId);
    }
}

Now both a1 and a2 produce the same hash code, so HashMap lookups work correctly.

What should a senior engineer know?

A senior engineer working on banking systems should understand the production implications of this concept.

  • Performance impact. Measure with JMH before making assumptions about which approach is faster.
  • Thread safety. In a multi-threaded banking service processing concurrent transactions, always consider whether the approach is thread-safe.
  • Memory footprint. Use JFR and VisualVM to measure actual memory usage under realistic banking workloads.
  • Monitoring. Set up JMX metrics and alerts for production banking applications.
JAVA
package com.bank.monitoring;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;

public class MemoryMonitor {
    public static void printMemoryUsage() {
        MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
        System.out.println("Heap: " + mem.getHeapMemoryUsage());
        System.out.println("Non-Heap: " + mem.getNonHeapMemoryUsage());
    }
}

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  a.equals(b)           → NPE if a is null
  Objects.equals(a, b)  → safe: handles null on both sides
  "literal".equals(var)  → safe: literal is never null
  Optional.ofNullable(x).orElse(default) → convert null first

Key Takeaway

Use Objects.equals(a, b) for null-safe comparison. It handles all null combinations without throwing NPE. In equals() implementations, use Objects.equals for each field comparison.

One sentence to remember: Objects.equals(a, b) is null-safe – use it instead of a.equals(b) when either side might be null.

---

Share
Use ← → arrow keys to navigate