it-swarm.com.de

Aufruf der Java varargs-Methode mit einem einzigen Nullargument?

Wenn ich eine vararg Java method foo(Object ...arg) und ich rufe foo(null, null) auf, habe ich sowohl arg[0] Als auch arg[1] als nulls. Aber wenn ich foo(null) aufrufe, ist arg selbst null. Warum passiert das?

Wie soll ich foo so aufrufen, dass foo.length == 1 && foo[0] == nulltrue ist?

88
pathikrit

Das Problem ist, dass, wenn Sie das Literal null verwenden, Java nicht weiß, welcher Typ es sein soll. Es kann sich um ein Null-Objekt oder ein Null-Objekt-Array handeln ein einziges Argument setzt es das letztere voraus.

Sie haben zwei Möglichkeiten. Wandeln Sie die Null explizit in Object um, oder rufen Sie die Methode mit einer stark typisierten Variablen auf. Siehe folgendes Beispiel:

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));
   }
}

Ausgabe:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
91
Mike Deck

Sie benötigen eine explizite Besetzung für Object:

foo((Object) null);

Andernfalls wird angenommen, dass das Argument das gesamte Array ist, das die varargs darstellen.

22
Bozho

Ein Testfall zur Veranschaulichung:

Der Java Code mit einer vararg-take Methodendeklaration (die zufällig statisch ist):

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;
    }
}

Dieser Java Code (ein JUnit4-Testfall) ruft das oben Gesagte auf (wir verwenden den Testfall, um nichts zu testen, nur um eine Ausgabe zu generieren):

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"}));
    }
}

Wenn Sie dies als JUnit-Test ausführen, erhalten Sie:

 sendNothing (): empfangenes 'x' ist ein Array der Größe 0 
 sendNullWithNoCast (): empfangenes 'x' ist null 
 sendNullWithCastToString (): empfangenes 'x' ist ein Array von Größe 1 
 sendNullWithCastToArray (): empfangenes 'x' ist null 
 sendOneValue (): empfangenes 'x' ist ein Array von Größe 1 
 sendThreeValues ​​(): empfangenes 'x' ist ein Array der Größe 3 
 sendArray (): empfangenes 'x' ist ein Array der Größe 3 

Um dies interessanter zu gestalten, rufen wir die Funktion receive() aus Groovy 2.1.2 auf und sehen, was passiert. Es stellt sich heraus, dass die Ergebnisse nicht gleich sind! Dies kann jedoch ein Fehler sein.

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"
    }

}

Wenn Sie diesen Test als JUnit-Test ausführen, erhalten Sie die folgenden Ergebnisse, wobei der Unterschied zu Java fett hervorgehoben ist.

 sendNothing (): empfangenes 'x' ist ein Array der Größe 0 
 sendNullWithNoCast (): empfangenes 'x' ist null 
sendNullWithCastToString (): empfangenes 'x' ist null
 sendNullWithCastToArray (): empfangenes 'x' ist null 
 sendOneValue (): empfangenes 'x' ist ein Array der Größe 1 
 sendThreeValues ​​(): empfangenes 'x' ist ein Array von Größe 3 
 sendArray (): empfangenes 'x' ist ein Array von Größe 3 
5
David Tonhofer

Dies liegt daran, dass eine varargs-Methode mit einem tatsächlichen Array anstelle einer Reihe von Array-Elementen aufgerufen werden kann. Wenn Sie es mit dem mehrdeutigen null versehen, wird angenommen, dass null ein Object[] Ist. Das Umwandeln von null in Object wird dies beheben.

3
ColinD

Ich bevorzuge

foo(new Object[0]);

um Nullzeigerausnahmen zu vermeiden.

Ich hoffe es hilft.

1
Deepak Garg

Die Reihenfolge für die Auflösung von Methodenüberladungen lautet ( https://docs.Oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. In der ersten Phase wird eine Überlastungsauflösung durchgeführt, ohne dass eine Boxing- oder Unboxing-Konvertierung oder die Verwendung eines Aufrufs mit variabler Aritätsmethode zulässig ist. Wird in dieser Phase keine geeignete Methode gefunden, fährt die Verarbeitung mit der zweiten Phase fort.

    Dies garantiert, dass alle Aufrufe, die in der Programmiersprache Java= vor Java SE 5.0) gültig waren, als Ergebnis der Einführung von Methoden mit variabler Arität nicht als mehrdeutig angesehen werden. implizites Boxing und/oder Unboxing Die Deklaration einer Methode mit variabler Arität (§8.4.1) kann jedoch die für einen bestimmten Methodenaufrufausdruck ausgewählte Methode ändern, da eine Methode mit variabler Arität in der ersten Methode als Methode mit fester Arität behandelt wird Zum Beispiel bewirkt das Deklarieren von m(Object...) in einer Klasse, die bereits m(Object) deklariert, m(Object) darf für einige Aufrufausdrücke (z. B. m (null)) nicht mehr ausgewählt werden, da m (Object []) spezifischer ist.

  2. Die zweite Phase führt eine Überlastungsauflösung durch, während das Ein- und Auspacken zugelassen wird, schließt jedoch die Verwendung des Aufrufs einer Methode mit variabler Arität aus. Wird in dieser Phase keine geeignete Methode gefunden, fährt die Verarbeitung mit der dritten Phase fort.

    Dies stellt sicher, dass eine Methode niemals durch einen Aufruf einer Methode mit variabler Arität ausgewählt wird, wenn sie durch einen Aufruf einer Methode mit fester Arität anwendbar ist.

  3. In der dritten Phase kann das Überladen mit Methoden mit variabler Arität, Boxen und Entboxen kombiniert werden.

foo(null) stimmt foo(Object... arg) mit arg = null in der ersten Phase überein. arg[0] = null Wäre die dritte Phase, die niemals passiert.

1
Alexey Romanov