用单个null参数调用Java varargs方法?


97

如果我有一个vararg Java方法foo(Object ...arg)并且调用了foo(null, null),则我同时拥有arg[0]arg[1]as null。但是,如果我呼叫foo(null),则arg本身为null。为什么会这样呢?

我应该怎么称呼foo这样foo.length == 1 && foo[0] == nulltrue

Answers:


95

问题是,当您使用原义null时,Java不知道它应该是哪种类型。它可以是空对象,也可以是空对象数组。对于单个参数,假定为后者。

您有两种选择。将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在样本中列出了方法及其目的,将对其他人有所帮助。
阿伦·库马尔

5
@ArunKumar asList()是该类的静态导入java.util.Arrays。我只是认为这很明显。尽管现在我正在考虑它,但我大概应该使用它,Arrays.toString()因为将它转换为List的唯一原因是它可以打印得漂亮。
Mike Deck

23

您需要显式转换为Object

foo((Object) null);

否则,假定参数为varargs表示的整个数组。


不完全是,请参阅我的文章。
Buhake Sindi

糟糕,这里...抱歉,午夜加油。
Buhake Sindi

6

一个测试用例来说明这一点:

带有vararg-take方法声明的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的数组

为了使这个更有趣,让我们receive()从Groovy 2.1.2 调用该函数,然后看看会发生什么。原来结果不一样!虽然这可能是一个错误。

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

这是因为可以使用实际数组而不是一系列数组元素来调用varargs方法。当您单独为其提供歧义时null,它会假定null是是Object[]。将转换nullObject可解决此问题。


1

我更喜欢

foo(new Object[0]);

避免Null指针异常。

希望能帮助到你。


14
好吧,如果这是一个vararg方法,为什么不直接调用like foo()呢?
BrainStorm.exe 2014年

@ BrainStorm.exe您的答案应标记为答案。谢谢。
MDP

1

方法重载解析的顺序为(https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2):

  1. 第一阶段执行重载解析,不允许装箱或拆箱转换,也不允许使用可变arity方法调用。如果在此阶段未找到适用的方法,则处理将继续进行到第二阶段。

    这样可以保证,由于引入了可变arity方法,隐式装箱和/或拆箱,在Java SE 5.0之前在Java编程语言中有效的任何调用都不会被认为是模棱两可的。但是,声明可变可变方法(第8.4.1节)可以更改为给定方法方法调用表达式选择的方法,因为可变可变方法在第一阶段被视为固定可变方法。例如,在已经声明了m(Object)的类中声明m(Object ...)会导致不再为某些调用表达式(例如m(null))选择m(Object),例如m(Object [] )更具体。

  2. 第二阶段执行重载解析,同时允许装箱和拆箱,但仍排除使用可变arity方法调用。如果在此阶段未找到适用的方法,则处理将继续进行到第三阶段。

    这样可以确保如果通过固定arity方法调用适用方法,则永远不会通过可变arity方法调用选择方法。

  3. 第三阶段允许将重载与可变arity方法,装箱和拆箱相结合。

foo(null)匹配foo(Object... arg)arg = null在第一阶段。 arg[0] = null将是第三阶段,这永远不会发生。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.