Home > Software design >  Java varargs method with single null argument
Java varargs method with single null argument

Time:06-10

I have a method taking a String varargs param locationIds, in which there's for loop on the ids

public static void validateLocationIds(String... locationIds) {
    for (String locationId : locationIds) {
        // do sth
    }
}

Now if I call validateLocationIds(var) where var of type String and value null, I expect NPE from the for loop because you can't do that on a null. And this is approved when I execute it from Intellij's Evaluate Expression while debugging, as it gave me NPE and the cause java.lang.NullPointerException: Cannot read the array length because "<local1>" is null.

However when the code is executed not from Evaluate Expression, there's no NPE and everything works fine.
Through debugging I found out it's because instead of being null, locationIds is converted to type String[1], value [null], making it possible to be processed by for loop.

So I'm confused how Java manged to convert null to [null], and why Evaluate Expression failed to do so.

CodePudding user response:

It's not about null specifically, it's about the null expression's type.

A null expression doesn't have to be typed, but it can be. Here is a trivial example:

String x = null;
System.out.println(x);

This is very slightly different compared to:

System.out.println(null);

Note that println has a boatload of overloads - completely different methods (and javac, the compiler, must pick which exact overload to invoke at compile time), that are all named println, all of which have 1 parameter. The first snippet is easy for the compiler: You clearly mean to call that println that takes a single String as parameter. However, the second is ambiguous. If I attempt to compile the second snippet, the compiler will literally tell me so:

error: reference to println is ambiguous

This shows how, even though the runtime behaviour of this would seem to be 100% identical (both null and x are expressions whose value will definitely be null), turns out, at least at compile time, this is not true.

The same principle can explain what you see here, with your varargs, in a slightly different way. Given:

void validateLocationIds(String... locationIds) {

That's just syntax sugar. Specifically, it's syntax sugar for:

  • The method's signature is validateLocationIds(String[] locationIds).
  • However, you are telling the compiler, during the compilation of calls to this method, to treat a sequence of arguments that can all be treated as String arguments to go ahead and treat that as if the caller had written validateLocationIds(new String[] {a, b, c}) instead of what you typed (validateLocationIds(a, b, c);).

Crucially the compiler will only do that if it has to - if the code would not compile if it didn't do this 'fold the args into an array'.

This then gets us (presumably, there are some more exotic alternate explanations, this is merely the most likely) to an explanation. Given:

String x = null;
validateLocationIds(x);

There is only one way for the compiler to make this work: By treating x as a String and therefore syntax sugaring that into new String[] {x}, i.e. a new string array of size 1 whose first and only entry is null. Because otherwise it would not compile; a variable of type String simply isn't a String[]. Even if the variable's value is currently null which is also a valid value for String[] - because how could the compiler possibly know that for sure? It can't, so it won't just go: Oohhh,, I see, a variable of type String, but, I know its null so I can treat it as whatever the heck I want, really. It does not do that.

In contrast to spelling it out literally:

validateLocationIds(null);

This is not ambiguous, unlike our println example: The Java Language Spec, in the section on picking between multiple different overloads, explicitly states that the compiler must error out with a 'call is ambiguous' error, but, when it's about whether to apply varargs sugaring or not, that is not the case: The array "wins", so to speak. In other words, if the call could both be treated as a straight method call (the expression is the array), as well as sugaring into an array (you could zip that one arg into a 1-len array and pass that instead), then 'just treat as array' wins.

This makes sense. Imagine that would be an error condition. Then you could NEVER pass an object array to void foo(Object...). After all, an array -is also an object-, and could therefore be 'zipped'.

CodePudding user response:

These do different things with your varargs method:

validateLocationIds((String)null);

validateLocationIds((String[])null);

The first passes a non-null array containing a null element, equivalent to new String[] { null }. The second passes null as the array directly.

Which of the two will happen depends on the compile-time type of your null variable.

  •  Tags:  
  • java
  • Related