0

Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls. The usecase I most often stumble upon this, is when dealing with DB calls, and parameters are abstracted to be passed via list. So it always have to be of certain length for any given procedure, and optional values are given as nulls.

I found Why does Map.of not allow null keys and values? which mentions List.of, but mainly talks about maps. And in the case of maps, I agree -- ambiguity of .get seems troublesome (key missing, or value intentionally null?), but this doesn't seem to apply to a list. If you get a null from a list, then you know for sure someone intentionally had put it there.

Some people might say "use optional", but I strongly believe it should be reserved for return types, and others seem to agree Uses for Optional

having an Optional in a class field or in a data structure, is considered a misuse of the API

What is the intended clean, standard solution? Does it really boil down to "use boilerplaty ArrayList initialization without these nice shorthand syntaxes" or "write your own util methods"?

Coderino Javarino
  • 2,819
  • 4
  • 21
  • 43
  • The intended clean solution is to not use `null` to avoid possible `NullPointerException`s when accessing the `List` contents. Since Java 8 every (?) new feature is built to avoid NPE. You have a special use case, and for that I'd suggest to create a custom static method that creates a `List` with nullable values. Give it a nice name so everyone knows what it does without looking at the source code. Something like `static listOfNullable(Object ... objects)`. Another solution could be to provide some singleton "empty" value instead of `null`. – Benjamin M Aug 20 '21 at 12:35

1 Answers1

3

Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls

This is incorrect:

List<String> list = Arrays.asList("a", "b", null);
String nullRef = list.get(2);
System.out.println(nullRef);

Works fine. List.of indeed doesn't accept null refs.

Some people might say "use optional"

Yeah, ignore those people.

What is the intended clean, standard solution?

Clean is a weaselword that is generally best read as: "I like it personally but am unwilling to just say that it's a taste preference" - it means nothing. You can't argue about taste, after all.

The standard solution, that is borderline objective language, we can work with that.

I think your API design that takes a list of db values is the problem. Presumably, you've written your own abstraction layer for this.

I'm having a hard time imaginine DB abstraction APIs where 'list of objects that includes null refs' is the right call.

If you're trying to weave prepared statement 'parameters', this would appear to be a nice API for that, and doesn't require lists at all:

db.select()
  .append("SELECT a, b FROM c INNER JOIN d ON c.e = d.f ")
  .append("WHERE (signup IS NULL OR signup < ?) ", LocalDate.now())
  .append("AND LEFT(username, ?) = ?", 2, "AA")
  .query()

If you're trying to abstract an insert/merge/upsert statement, you need some way of linking values with the column name that the value is a value for. This would suggest an API along the lines of:

db.insert("tableName")
  .mergeOn("key", key)
  .mergeOn("date", someDate)
  .put("value", value)
  .exec();

You can also re-use Map.of for this purpose, in which case 'I want the db to use the default value for this column' is done by simply not including that key/value pair in your map, thus sidestepping your needs to put null in such a map.

Perhaps if you show your specific API use-case, more insights can be provided.

If your question boils down to:

"I want to put null refs and use List.of, what is the standard clean way to do that"

then the answer is a rather obvious: There is nothing - as that does not work.

There is no --shut-up-List.of--let-me-add-nulls command line switch.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • .. thanks about correcting me about Arrays.asList, I feel silly now. Regarding DB api -- it's only used for passing oracle's struct arrays parameter into the stored procedure. Caller gives a List (no null elements here), and a mapper T -> List (can have null elements), which converts pojo entity into column values – Coderino Javarino Aug 20 '21 at 12:48
  • 1
    Seems like a case of too many layers of abstraction. If you have a mapper, that mapper should just straight up map your `T` into a filled-in PreparedStatement. Until you're at the phase where you can make a PS, just pass the T and the mapper around. That gives the mapper various opportunities for efficiency and custom SQL and such. Maybe it wants to make a custom enum type db-side, who knows. Maybe it wants to call `.setBlob` or `.setArray`. – rzwitserloot Aug 20 '21 at 14:54
  • 1
    For completeness, you can construct an immutable list potentially containing `null` in recent JDKs, via e.g. `Stream.of("foo", "bar", null).toList()`. In the reference implementation, it’s even the case that `Stream.of("foo", "bar", null).toList().getClass() == List.of("foo", "bar", "not null").getClass()`. However, that’s an implementation detail and doesn’t change the fact that there’s a behavioral difference between the list that strictly disallows `null` and the other. I’d stay with `Arrays.asList(…)` when you still have to deal with `null`. And yes, fixing the design is the better option. – Holger Aug 23 '21 at 15:18