如果我有一个vararg Java方法foo(Object ...arg)
而我致电foo(null, null)
,我arg[0]
和arg[1]
都为null
。但是如果我拨打foo(null)
,arg
本身就是空的。为什么会这样?
如何致电foo
以foo.length == 1 && foo[0] == null
为true
?
答案 0 :(得分:90)
问题在于,当您使用文字null时,Java不知道它应该是什么类型。它可以是null Object,也可以是null Object数组。对于单个参数,它假设后者。
你有两个选择。将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]
答案 1 :(得分:22)
您需要明确转换为Object
:
foo((Object) null);
否则,该参数被假定为varargs所代表的整个数组。
答案 2 :(得分:5)
一个测试用例来说明这一点:
带有vararg-taking方法声明的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(): 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
为了使这更有趣,让我们从Groovy 2.1.2调用receive()
函数,看看会发生什么。事实证明结果不一样!这可能是个错误。
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(): 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
答案 3 :(得分:3)
这是因为可以使用实际数组而不是一系列数组元素调用varargs方法。当您为其提供含糊不清的null
时,它会假定null
是Object[]
。将null
投射到Object
会解决此问题。
答案 4 :(得分:1)
我更喜欢
foo(new Object[0]);
避免空指针异常。
希望它有所帮助。
答案 5 :(得分:1)
方法重载解析的顺序为(https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2):
第一阶段执行重载解析,不允许装箱或拆箱转换,也不允许使用可变arity方法调用。如果在此阶段未找到适用的方法,则处理将继续进行到第二阶段。
这保证了,由于引入了可变arity方法,隐式装箱和/或拆箱,在Java SE 5.0之前的Java编程语言中有效的任何调用都不会被认为是模棱两可的。但是,声明可变可变方法(第8.4.1节)可以更改为给定方法方法调用表达式选择的方法,因为可变可变方法在第一阶段被视为固定可变方法。例如,在已经声明了m(Object)的类中声明m(Object ...)会导致不再为某些调用表达式(例如m(null))选择m(Object),例如m(Object [] )更具体。
第二阶段执行重载解析,同时允许装箱和拆箱,但仍排除使用可变arity方法调用。如果在此阶段未找到适用的方法,则处理将继续进行到第三阶段。
这确保了如果通过固定arity方法调用适用方法,则永远不会通过可变arity方法调用选择方法。
第三阶段允许将重载与可变arity方法,装箱和拆箱相结合。
foo(null)
在第一阶段将foo(Object... arg)
与arg = null
匹配。 arg[0] = null
将是第三阶段,永远不会发生。