🧰 Core APIs Toolbox · Intermediate

The Objects class in Java

equals, hash, requireNonNull, toString with default, isNull.

🧩 The mysteryYour equals method works perfectly — until one object has a null name and the whole HashMap lookup crashes. One helper class makes this whole category of crash disappear.

A null-safe toolbox

**java.util.Objects** is full of static helpers that handle null for you. **Objects.equals(a, b)** returns true if both are null, false if only one is, and otherwise calls a.equals(b) — you never call a method on null.

🔮 Predict it

Comparing with nulls

What does this print?

String a = null;
String b = null;
System.out.println(Objects.equals(a, b));
System.out.println(Objects.equals(a, "x"));
  1. true false
  2. false false
  3. Throws NullPointerException
Show the answer

true — two nulls count as equal. false — only one is null. No exception, because Objects.equals never calls a method on null.

Writing equals by hand

✗ Crashes on null
return age == p.age
    && name.equals(p.name);

If this.name is null, name.equals(...) throws NullPointerException.

✓ Null-safe
return age == p.age
    && Objects.equals(name, p.name);

Handles null on either side.

Hashing fields

**Objects.hash(f1, f2, …)** combines fields into one hash code — the perfect partner for equals. Records generate both for you.

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

Fail fast with requireNonNull

**Objects.requireNonNull(x, "msg")** throws a NullPointerException with your message right where the bad value enters — instead of failing later at some confusing spot.

Pet(String name) {
    this.name = Objects.requireNonNull(
        name, "name is required");
}
🔮 Predict it

Friendly fallbacks

What does this print?

String n = null;
System.out.println(
    Objects.toString(n, "n/a"));
System.out.println(
    Objects.requireNonNullElse(n, "guest"));
  1. null guest
  2. n/a guest
  3. Throws NullPointerException
Show the answer

**toString(o, default) returns the default text for null, and requireNonNullElse(x, d)** returns d when x is null.

Predicates for streams

**Objects::isNull and Objects::nonNull** are ready-made predicates — filter(Objects::nonNull) is a common idiom to drop nulls from a stream.

List<String> clean = Stream.of("a", null, "b")
    .filter(Objects::nonNull)
    .toList(); // [a, b]
💼 In the real world

Defensive code that reads well

Constructors that start with requireNonNull checks turn mysterious NPEs deep in business logic into clear errors at the boundary, naming the culprit. Most hand-written equals/hashCode in production uses Objects.equals and Objects.hash.

Key takeaways

  1. Objects.equals(a, b) is safe even when a is null
  2. Objects.hash(f1, f2…) combines fields for hashCode()
  3. requireNonNull(x, "msg") throws NPE early with a message
  4. Objects::isNull and Objects::nonNull work as Predicates
🤯 Did you know?

Since Java 14, NullPointerException messages are "helpful": they name exactly what was null, like Cannot invoke "String.length()" because "name" is null.

Practice questions

What does this print?

String a = null;
System.out.println(Objects.equals(a, "x"));
System.out.println(Objects.toString(a, "n/a"));
  1. false null
  2. false n/a
  3. Throws NullPointerException
  4. true n/a
Check your answer

false n/a. Objects.equals handles the null safely and returns false. Objects.toString returns the default text when the object is null.

What does this print?

String name = null;
String n = Objects.requireNonNull(
    name, "name is required");
System.out.println(n.length());
  1. 0
  2. null
  3. Throws NullPointerException
  4. Compile error
Check your answer

Throws NullPointerException. requireNonNull throws a NullPointerException with your message right where the bad value enters, instead of failing later at some confusing spot.

Next: random numbers — and why new Random(42) gives the *same* "random" numbers every single time.