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


97

vararg Javaメソッドがfoo(Object ...arg)あり、を呼び出すfoo(null, null)場合、両方arg[0]arg[1]nullsがあります。しかし、を呼び出すとfoo(null)argそれ自体はnullになります。なぜこうなった?

どのようにして呼び出す必要がありfoo、そのようなことはfoo.length == 1 && foo[0] == nullありますかtrue

回答:


95

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

asListサンプルにメソッドとその目的を記載していれば、他の人に役立つでしょう。
アルンクマール2014年

5
@ArunKumar asList()java.util.Arraysクラスからの静的インポートです。私はそれが明白であると思いました。今はそれについて考えていArrays.toString()ますが、リストに変換される唯一の理由はきれいに印刷されるためです。
Mike Deck、

23

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

foo((Object) null);

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


いいえ、私の投稿をご覧ください。
Buhake Sindi

おっと、ここも同じ…ごめん、真夜中のオイル。
Buhake Sindi、

6

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

可変引数を取るメソッド宣言を含む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の配列です

これをもっと面白くするために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'がnullです
sendNullWithCastToString():受信した 'x'がnullです
sendNullWithCastToArray():受信した 'x'がnullです
sendOneValue():受信した 'x'はサイズ1の配列です
sendThreeValues():受信した 'x'はサイズ3の配列です
sendArray():受信した 'x'はサイズ3の配列です

これは、パラメーターなしで可変個関数を呼び出すことも考慮しています
bebbo

3

これは、varargsメソッドを一連の配列要素ではなく実際の配列で呼び出すことができるためです。あなたが曖昧でそれを提供する場合null、それ自体で、それが前提としてnullいますObject[]。をキャストするnullと、Objectこれが修正されます。


1

私は好む

foo(new Object[0]);

Nullポインタ例外を回避するため。

それが役に立てば幸い。


14
まあ、それが可変引数メソッドである場合は、なぜそれを次のように呼び出すだけ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. 最初のフェーズでは、ボックス化またはボックス化解除の変換、または変数アリティメソッドの呼び出しを使用せずに、過負荷の解決を実行します。このフェーズ中に該当するメソッドが見つからない場合、処理は第2フェーズに進みます。

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

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

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

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

foo(null)最初のフェーズfoo(Object... arg)と一致しarg = nullます。 arg[0] = null決して起こらない第三段階です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.