Vararg Java method foo(Object ...arg)
があり、foo(null, null)
を呼び出す場合、null
sとして_arg[0]
_と_arg[1]
_の両方があります。 。しかし、foo(null)
を呼び出すと、arg
自体がnullになります。
_foo.length == 1 && foo[0] == null
_がfoo
になるようにtrue
を呼び出すにはどうすればよいですか?
問題は、リテラルnullを使用すると、Javaはどのタイプであるかがわからない。nullオブジェクトまたはnullオブジェクト配列になる可能性があることです。後者を前提とする単一の引数。
2つの選択肢があります。 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]
Object
への明示的なキャストが必要です:
foo((Object) null);
それ以外の場合、引数は可変引数が表す配列全体であると想定されます。
これを説明するテストケース:
可変引数を取るメソッド宣言を伴う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'はnull sendNullWithCastToString():受信した 'x'は配列ですサイズ1 sendNullWithCastToArray()の: 'x'がnull sendOneValue()を受信: 'x'はサイズ1 sendThreeValues()の配列: 'x'を受信サイズ3 sendArray()の配列:受信した 'x'はサイズ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():受信した 'x'はサイズ0の配列です。 sendNullWithNoCast():受信した 'x'はnull sendNullWithCastToString():受信した 'x'はnull sendNullWithCastToArray():受信した 'x'はnull sendOneValue():受信した 'x'はサイズ1の配列です。 sendThreeValues():受信した 'x'は配列ですサイズ3 sendArray()の: 'x'はサイズ3 の配列です
これは、varargsメソッドを一連の配列要素ではなく、実際の配列で呼び出すことができるためです。曖昧なnull
を単独で提供すると、null
がObject[]
。 null
をObject
にキャストすると、これが修正されます。
私は好む
foo(new Object[0]);
nullポインター例外を回避します。
それが役に立てば幸い。
メソッドのオーバーロード解決の順序は( https://docs.Oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):
最初のフェーズでは、ボックス化またはボックス化解除の変換、または可変アリティメソッド呼び出しの使用を許可せずにオーバーロード解決を実行します。このフェーズで適用可能なメソッドが見つからない場合、処理は2番目のフェーズに進みます。
これにより、Java SE 5.0より前のJavaプログラミング言語で有効だった呼び出しが、可変アリティメソッド、暗黙的なボックス化および/またはボックス化解除の結果として曖昧と見なされないことが保証されます。 。ただし、可変アリティメソッド(§8.4.1)の宣言は、特定のメソッドメソッド呼び出し式に選択されたメソッドを変更できます。これは、可変アリティメソッドが最初のフェーズで固定アリティメソッドとして扱われるためです。たとえば、すでにm(Object...)を宣言しているクラスでm(Object)を宣言すると、m(Object)が選択されなくなりますm(Object [])がより具体的であるため、一部の呼び出し式(m(null)など)。
2番目のフェーズでは、ボックス化とボックス化解除を許可しながらオーバーロード解決を実行しますが、可変アリティメソッド呼び出しの使用はまだできません。このフェーズ中に該当するメソッドが見つからない場合、処理は第3フェーズに進みます。
これにより、固定アリティメソッドの呼び出しで適用されるメソッドが、可変アリティメソッドの呼び出しで選択されないことが保証されます。
3番目のフェーズでは、オーバーロードを可変アリティメソッド、ボックス化、およびボックス化解除と組み合わせることができます。
foo(null)
は、最初のフェーズでfoo(Object... arg)
とarg = null
と一致します。 arg[0] = null
は3番目のフェーズであり、決して発生しません。