java - Avoiding null attributes in records - Stack Overflow

admin2025-04-19  0

I have the following record. But the collection cannot be null. Is there a way to default it to an empty list if it's null?

public record Car(
        long id,
        String model,
        List<Wheel> wheels
) {
}

I could add a custom constructor like below but are there any better ways to achieve it?

public record Car(
        long id,
        String model,
        List<Wheel> wheels
) {
    public Car {
        if (wheels == null) {
            wheels = new ArrayList<>();
        }
    }
}

I have the following record. But the collection cannot be null. Is there a way to default it to an empty list if it's null?

public record Car(
        long id,
        String model,
        List<Wheel> wheels
) {
}

I could add a custom constructor like below but are there any better ways to achieve it?

public record Car(
        long id,
        String model,
        List<Wheel> wheels
) {
    public Car {
        if (wheels == null) {
            wheels = new ArrayList<>();
        }
    }
}
Share Improve this question asked Mar 4 at 0:10 S.DanS.Dan 1,9306 gold badges35 silver badges59 bronze badges 3
  • 4 "Better" is subjective ... but I'd suggest raising an exception rather than adding an empty list of wheels by default. – Stephen C Commented Mar 4 at 0:46
  • Like Stephen said, it’s usually better to put Objects.requireNonNull(wheels, "Wheels cannot be null."); in your constructor. If someone wants to pass an empty List, they should pass an empty List. null should not be treated as a synonym for an empty collection. – VGR Commented Mar 4 at 1:48
  • A record should be treated as an immutable structure. You should probably pass an immutable list to your Car when creating it. If you need to do more complex logic, use a standard class. – Mr. Polywhirl Commented Mar 4 at 2:23
Add a comment  | 

1 Answer 1

Reset to default 3

null is a bit of an odd beast in java; it means too many things if you take a wide survey across the community.

But that doesn't mean you should make the same mistake. null is best left as a thing you 'tighten' - it needs to mean something highly specific. And that specific thing is: "Unknown / irrelevant / not set".

Here's the difference:

If I hold my 2 hands behind my back with closed fists, and I ask you: "Are the object that I hold in my left hand, and the one in my right hand, equal?" - then you should ask me to show you the objects. If I refuse, the answer is not "They are equal". The answer is also not "They are different". The only right answer is "I cannot tell."

Hence, this java code:

public void whatever() {
  String a = null;
  String b = null;
  if (a.equals(b)) { ... }
}

Actually does the right thing: It throws an NPE which is what you want. Similarly, asking for the length of an unknown thing is best answered with 'I do not know'. The answer: "It has 0 length" is not appropriate.

By approaching null that way, i.e. by ensuring you never use it as a standin for 'empty' or 'default' or similar, the NullPointerException turns into an asset instead of a nuisance. You want that exception.

Hence, the right approach is in fact this:

public record Car(
        long id,
        String model,
        List<Wheel> wheels
) {
    public Car {
        Objects.requireNonNull(wheels, "wheels");
    }
}

The constructor of records is the right place for checking preconditions, and this is a precondition: The list is required to be non-null. You might also want to require that model isn't null either.

A clue you're doing it wrong is if you write if (x == null || x.isEmpty()) a lot. After all, if 'null' and 'empty' are treated as semantically equivalent pretty much all the time then why is null a thing in the first place? It's not hard to make an empty thing. In fact, for strings, it's easier: "" is shorter to write than null and takes, if anything, less memory (the empty string is interned, don't worry about that).

Your callers shouldn't be overly bothered, they can just pass List.of() which is not much to type and far easier to read. After all, this:

new Car(0, null, null);
new Car(0, "", List.of());

The second one is more informative; at least you have some vague idea about the types of the arguments, and you also know clearly that the list is supposed to be empty.

null can still make sense, if the concept of 'irrelevant/unknown' is something your record needs to be capable of conveying. For example, if you want to be able to encode the notion of a 'car' that is currently in the garage, with the wheels removed. It still has wheels, we just don't know where they are right now. This is annoying, of course: All code that deals with Car instances needs to be capable of dealing with it. Hence, you only 'do' that (allow null) if it's strictly necessary.

A wheel-less car, that's no problem, and should be represented with List.of(). Not null.

The simple way to think about it is: null should mean something different vs. any other possible value imaginable. If that is true, null is good. If that is not true (for example, null means the same thing as List.of()), then you should throw an NPE as fast as you can (first line of a constructor is a great place).

Immutability

records don't need to be immutable but it tends to be expected. Did you intend for this record to allow the set of wheels to change during its lifetime? If yes, record might not be appropriate. If no, then new ArrayList is not right; you want List.of which guarantees that the list cannot change. It's a bit difficult to enforce that callers pass an immutable list, unfortunately. Best simply not do that (document it instead). Also, List.of() is shorter and (very rarely relevant) more efficient.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745066523a283019.html

最新回复(0)