Calling Java varargs method with single null argument?
Asked Answered
A

6

111

If I have a vararg Java method foo(Object ...arg) and I call foo(null, null), I have both arg[0] and arg[1] as nulls. But if I call foo(null), arg itself is null. Why is this happening?

How should I call foo such that foo.length == 1 && foo[0] == null is true?

Ayah answered 26/10, 2010 at 21:10 Comment(0)
C
104

The issue is that when you use the literal null, Java doesn't know what type it is supposed to be. It could be a null Object, or it could be a null Object array. For a single argument it assumes the latter.

You have two choices. Cast the null explicitly to Object or call the method using a strongly typed variable. See the example below:

public class Temp{
   public static void main(String[] args){
      foo("a", "b", "c");
      foo(null, null);
      foo((Object)null);
      Object bar = null;
      foo(bar);
   }

   private static void foo(Object...args) {
      System.out.println("foo called, args: " + asList(args));
   }
}

Output:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Christophany answered 26/10, 2010 at 21:17 Comment(3)
Would be helpful to others if you had listed the asList method in the sample, and its purpose.Ferrick
@ArunKumar asList() is a static import from the java.util.Arrays class. I just assumed it was obvious. Although now that I'm thinking about it, I probably should have just used Arrays.toString() since the only reason it gets converted to a List is so it will print pretty.Christophany
Additional reference: https://mcmap.net/q/194654/-calling-java-varargs-method-with-single-null-argumentCoronary
P
30

You need an explicit cast to Object:

foo((Object) null);

Otherwise the argument is assumed to be the whole array that the varargs represents.

Protagonist answered 26/10, 2010 at 21:14 Comment(1)
Oops, same here...sorry, midnight oil.Detriment
M
9

A Test Case to illustrate this:

The Java code with a vararg-taking method declaration (which happens to be static):

public class JavaReceiver {
    public static String receive(String... x) {
        String res = ((x == null) ? "null" : ("an array of size " + x.length));
        return "received 'x' is " + res;
    }
}

This Java code (a JUnit4 test case) calls the above (we are using the test case not to test anything, just to generate some output):

import org.junit.Test;

public class JavaSender {

    @Test
    public void sendNothing() {
        System.out.println("sendNothing(): " + JavaReceiver.receive());
    }

    @Test
    public void sendNullWithNoCast() {
        System.out.println("sendNullWithNoCast(): " + JavaReceiver.receive(null));
    }

    @Test
    public void sendNullWithCastToString() {
        System.out.println("sendNullWithCastToString(): " + JavaReceiver.receive((String)null));
    }

    @Test
    public void sendNullWithCastToArray() {
        System.out.println("sendNullWithCastToArray(): " + JavaReceiver.receive((String[])null));
    }

    @Test
    public void sendOneValue() {
        System.out.println("sendOneValue(): " + JavaReceiver.receive("a"));
    }

    @Test
    public void sendThreeValues() {
        System.out.println("sendThreeValues(): " + JavaReceiver.receive("a", "b", "c"));
    }

    @Test
    public void sendArray() {
        System.out.println("sendArray(): " + JavaReceiver.receive(new String[]{"a", "b", "c"}));
    }
}

Running this as a JUnit test yields:

sendNothing(): received 'x' is an array of size 0
sendNullWithNoCast(): received 'x' is null
sendNullWithCastToString(): received 'x' is an array of size 1
sendNullWithCastToArray(): received 'x' is null
sendOneValue(): received 'x' is an array of size 1
sendThreeValues(): received 'x' is an array of size 3
sendArray(): received 'x' is an array of size 3

To make this more interesting, let's call the receive() function from Groovy 2.1.2 and see what happens. It turns out that the results are not the same! This may be a bug though.

import org.junit.Test

class GroovySender {

    @Test
    void sendNothing() {
        System.out << "sendNothing(): " << JavaReceiver.receive() << "\n"
    }

    @Test
    void sendNullWithNoCast() {
        System.out << "sendNullWithNoCast(): " << JavaReceiver.receive(null) << "\n"
    }

    @Test
    void sendNullWithCastToString() {
        System.out << "sendNullWithCastToString(): " << JavaReceiver.receive((String)null) << "\n"
    }

    @Test
    void sendNullWithCastToArray() {
        System.out << "sendNullWithCastToArray(): " << JavaReceiver.receive((String[])null) << "\n"
    }

    @Test
    void sendOneValue() {
        System.out << "sendOneValue(): " + JavaReceiver.receive("a") << "\n"
    }

    @Test
    void sendThreeValues() {
        System.out << "sendThreeValues(): " + JavaReceiver.receive("a", "b", "c") << "\n"
    }

    @Test
    void sendArray() {
        System.out << "sendArray(): " + JavaReceiver.receive( ["a", "b", "c"] as String[] ) << "\n"
    }

}

Running this as a JUnit test yields the following, with the difference to Java highlighted in bold.

sendNothing(): received 'x' is an array of size 0
sendNullWithNoCast(): received 'x' is null
sendNullWithCastToString(): received 'x' is null
sendNullWithCastToArray(): received 'x' is null
sendOneValue(): received 'x' is an array of size 1
sendThreeValues(): received 'x' is an array of size 3
sendArray(): received 'x' is an array of size 3
Mig answered 6/5, 2013 at 17:58 Comment(1)
this considers also calling the variadic function without parameterKean
M
3

This is because a varargs method can be called with an actual array rather than a series of array elements. When you provide it with the ambiguous null by itself, it assumes the null is an Object[]. Casting the null to Object will fix this.

Migraine answered 26/10, 2010 at 21:16 Comment(0)
V
2

The ordering for method overloading resolution is (https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2):

  1. The first phase performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

    This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

  2. The second phase performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

    This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

  3. The third phase allows overloading to be combined with variable arity methods, boxing, and unboxing.

foo(null) matches foo(Object... arg) with arg = null in the first phase. arg[0] = null would be the third phase, which never happens.

Vaginal answered 8/4, 2019 at 19:41 Comment(0)
U
1

I prefer

foo(new Object[0]);

to avoid Null pointer exceptions.

Hope it helps.

Ure answered 25/11, 2013 at 23:5 Comment(2)
Well, if it is a vararg method, why not just call it like foo()?Denominate
@BrainStorm.exe your answer should be marked as the answer. thanks.Gospodin

© 2022 - 2024 — McMap. All rights reserved.