调用只有一个null参数的Java可变参数方法?

110

如果我有一个可变参数 Java 方法 foo(Object ...arg),并且我调用 foo(null, null),那么我会得到 arg[0]arg[1] 都是 null。但是如果我调用 foo(null)arg 本身就是 null。为什么会这样发生?

我应该如何调用 foo 使得 foo.length == 1 && foo[0] == null 返回 true

6个回答

103

问题在于,当您使用字面量null时,Java不知道它应该是什么类型。它可以是一个null对象,也可以是一个null对象数组。对于单个参数,它假定是后者。

您有两个选择。将null显式转换为Object或使用强类型变量调用方法。请参见下面的示例:

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));
   }
}

输出:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]

如果您在示例中列出了asList方法及其用途,将对其他人有所帮助。 - Arun Kumar
6
asList()java.util.Arrays类的静态导入。我只是认为这是显而易见的。虽然现在我在考虑,我可能应该使用Arrays.toString(),因为它被转换为列表的唯一原因是为了打印漂亮。 - Mike Deck
附加参考:https://dev59.com/hm865IYBdhLWcg3wBJ0q#55580650 - Gerardo Cauich

29
您需要显式转换为Object:
foo((Object) null);
否则,假定该参数是varargs所表示的整个数组。

抱歉,我也是这样...熬夜了。 - Buhake Sindi

7

一个用来说明的测试案例:

带有可变参数方法声明的Java代码(这个方法是静态的):

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;
    }
}

这段Java代码(一个JUnit4测试用例)调用了上面的代码(我们使用这个测试用例不是为了测试任何东西,而是为了生成一些输出):
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"}));
    }
}

作为JUnit测试运行此代码的结果如下:
sendNothing(): 接收到的 'x' 是大小为0的数组
sendNullWithNoCast(): 接收到的 'x' 为空
sendNullWithCastToString(): 接收到的 'x' 是大小为1的数组
sendNullWithCastToArray(): 接收到的 'x' 为空
sendOneValue(): 接收到的 'x' 是大小为1的数组
sendThreeValues(): 接收到的 'x' 是大小为3的数组
sendArray(): 接收到的 'x' 是大小为3的数组
为了使这更有趣,让我们从Groovy 2.1.2中调用receive()函数并看看会发生什么。结果表明,结果并不相同!虽然这可能是一个bug。
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"
    }

}

将其作为JUnit测试运行,将产生以下结果,Java与之间的差异以粗体突出显示。

sendNothing():接收到的“x”是大小为0的数组
sendNullWithNoCast():接收到的“x”为空
sendNullWithCastToString():接收到的“x”为空
sendNullWithCastToArray():接收到的“x”为空
sendOneValue():接收到的“x”是大小为1的数组
sendThreeValues():接收到的“x”是大小为3的数组
sendArray():接收到的“x”是大小为3的数组

这也考虑了在没有参数的情况下调用可变参数函数。 - bebbo

3
这是因为可变参数方法可以使用实际数组而不是一系列数组元素进行调用。当您仅提供含糊不清的null时,它会假定null是一个Object[]。将null强制转换为Object即可解决此问题。

2
方法重载解析的顺序为(https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2):
第一阶段执行重载决策,不允许装箱或拆箱转换,也不允许使用可变元方法调用。如果在此阶段未找到适用的方法,则处理继续到第二阶段。
这保证了在 Java SE 5.0 之前有效的任何调用不会因为可变元方法、隐式装箱和/或拆箱的引入而被认为是模糊的。然而,变量元方法(§8.4.1)的声明可以改变给定方法调用表达式选择的方法,因为在第一阶段中,可变元方法被视为固定元方法。例如,在已经声明m(Object)的类中声明m(Object...)会导致对于某些调用表达式(如m(null)),不再选择m(Object),因为m(Object[])更具体。
第二阶段执行重载决策,允许装箱和拆箱,但仍禁止使用可变元方法调用。如果在此阶段未找到适用的方法,则处理继续到第三阶段。
这确保了如果通过固定元方法调用适用,则永远不会通过可变元方法调用选择方法。
第三阶段允许将重载与可变元方法、装箱和拆箱相结合。

foo(null)foo(Object... arg) 在第一阶段匹配,其中arg = nullarg[0] = null 应该是第三阶段,但这一步骤从未发生。


1
我更喜欢。
foo(new Object[0]);

避免空指针异常。

希望能够帮到您。


15
如果这是一个可变参数方法,为什么不直接像 foo() 这样调用它呢? - BrainStorm.exe
@BrainStorm.exe 你的答案应该被标记为答案。谢谢。 - M D P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接