web-dev-qa-db-ja.com

単一のnull引数でJava varargsメソッドを呼び出しますか?

Vararg Java method foo(Object ...arg)があり、foo(null, null)を呼び出す場合、nullsとして_arg[0]_と_arg[1]_の両方があります。 。しかし、foo(null)を呼び出すと、arg自体がnullになります。

_foo.length == 1 && foo[0] == null_がfooになるようにtrueを呼び出すにはどうすればよいですか?

88
pathikrit

問題は、リテラル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]
91
Mike Deck

Objectへの明示的なキャストが必要です:

foo((Object) null);

それ以外の場合、引数は可変引数が表す配列全体であると想定されます。

22
Bozho

これを説明するテストケース:

可変引数を取るメソッド宣言を伴う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 
の配列です
5
David Tonhofer

これは、varargsメソッドを一連の配列要素ではなく、実際の配列で呼び出すことができるためです。曖昧なnullを単独で提供すると、nullObject[]nullObjectにキャストすると、これが修正されます。

3
ColinD

私は好む

foo(new Object[0]);

nullポインター例外を回避します。

それが役に立てば幸い。

1
Deepak Garg

メソッドのオーバーロード解決の順序は( https://docs.Oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. 最初のフェーズでは、ボックス化またはボックス化解除の変換、または可変アリティメソッド呼び出しの使用を許可せずにオーバーロード解決を実行します。このフェーズで適用可能なメソッドが見つからない場合、処理は2番目のフェーズに進みます。

    これにより、Java SE 5.0より前のJavaプログラミング言語で有効だった呼び出しが、可変アリティメソッド、暗黙的なボックス化および/またはボックス化解除の結果として曖昧と見なされないことが保証されます。 。ただし、可変アリティメソッド(§8.4.1)の宣言は、特定のメソッドメソッド呼び出し式に選択されたメソッドを変更できます。これは、可変アリティメソッドが最初のフェーズで固定アリティメソッドとして扱われるためです。たとえば、すでにm(Object...)を宣言しているクラスでm(Object)を宣言すると、m(Object)が選択されなくなりますm(Object [])がより具体的であるため、一部の呼び出し式(m(null)など)。

  2. 2番目のフェーズでは、ボックス化とボックス化解除を許可しながらオーバーロード解決を実行しますが、可変アリティメソッド呼び出しの使用はまだできません。このフェーズ中に該当するメソッドが見つからない場合、処理は第3フェーズに進みます。

    これにより、固定アリティメソッドの呼び出しで適用されるメソッドが、可変アリティメソッドの呼び出しで選択されないことが保証されます。

  3. 3番目のフェーズでは、オーバーロードを可変アリティメソッド、ボックス化、およびボックス化解除と組み合わせることができます。

foo(null)は、最初のフェーズでfoo(Object... arg)arg = nullと一致します。 arg[0] = nullは3番目のフェーズであり、決して発生しません。

1
Alexey Romanov