Vous appelez la méthode Java varargs avec un seul argument nul?

97

Si j'ai une méthode Java vararg foo(Object ...arg)et que j'appelle foo(null, null), j'ai les deux arg[0]et arg[1]comme nulls. Mais si j'appelle foo(null), arglui-même est nul. Pourquoi cela arrive-t-il?

Comment dois - je appeler fooce qui foo.length == 1 && foo[0] == nullest true?

pathikrit
la source

Réponses:

95

Le problème est que lorsque vous utilisez le littéral null, Java ne sait pas de quel type il est censé être. Il peut s'agir d'un objet nul ou d'un tableau d'objets nul. Pour un seul argument, il suppose ce dernier.

Vous avez deux choix. Convertissez explicitement la valeur null en Object ou appelez la méthode à l'aide d'une variable fortement typée. Voir l'exemple ci-dessous:

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

Production:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Mike Deck
la source
Serait utile aux autres si vous aviez énuméré la asListméthode dans l'échantillon et son objectif.
Arun Kumar
5
@ArunKumar asList()est une importation statique de la java.util.Arraysclasse. J'ai juste supposé que c'était évident. Bien que maintenant que j'y pense, j'aurais probablement dû l'utiliser Arrays.toString()car la seule raison pour laquelle il est converti en liste est qu'il s'imprimera assez.
Mike Deck
23

Vous avez besoin d'un casting explicite pour Object:

foo((Object) null);

Sinon, l'argument est supposé être le tableau entier que représente les varargs.

Bozho
la source
Pas vraiment, voir mon post.
Buhake Sindi
Oups, même ici ... désolé, huile de minuit.
Buhake Sindi
6

Un cas de test pour illustrer ceci:

Le code Java avec une déclaration de méthode prenant vararg (qui se trouve être statique):

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

Ce code Java (un cas de test JUnit4) appelle ce qui précède (nous utilisons le cas de test pour ne rien tester, juste pour générer une sortie):

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

L'exécuter en tant que test JUnit donne:

sendNothing (): reçu 'x' est un tableau de taille 0
sendNullWithNoCast (): reçu 'x' est nul
sendNullWithCastToString (): reçu 'x' est un tableau de taille 1
sendNullWithCastToArray (): reçu 'x' est nul
sendOneValue (): reçu 'x' est un tableau de taille 1
sendThreeValues ​​(): reçu 'x' est un tableau de taille 3
sendArray (): reçu 'x' est un tableau de taille 3

Pour rendre cela plus intéressant, appelons la receive()fonction de Groovy 2.1.2 et voyons ce qui se passe. Il s'avère que les résultats ne sont pas les mêmes! Cela peut cependant être un bug.

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

}

L'exécution de ceci en tant que test JUnit donne les résultats suivants, la différence avec Java étant mise en évidence en gras.

sendNothing (): reçu 'x' est un tableau de taille 0
sendNullWithNoCast (): reçu 'x' est nul
sendNullWithCastToString (): reçu 'x' est nul
sendNullWithCastToArray (): reçu 'x' est nul
sendOneValue (): reçu 'x' est un tableau de taille 1
sendThreeValues ​​(): reçu 'x' est un tableau de taille 3
sendArray (): reçu 'x' est un tableau de taille 3
David Tonhofer
la source
cela considère également l'appel de la fonction variadique sans paramètre
bebbo
3

En effet, une méthode varargs peut être appelée avec un tableau réel plutôt qu'avec une série d'éléments de tableau. Lorsque vous lui fournissez l'ambiguïté nullpar lui-même, il suppose que nullc'est un Object[]. Lancer le nullvers Objectrésoudra ce problème.

ColinD
la source
1

je préfère

foo(new Object[0]);

pour éviter les exceptions de pointeur Null.

J'espère que ça aide.

Deepak Garg
la source
14
Eh bien, si c'est une méthode vararg, pourquoi ne pas l'appeler comme ça foo()?
BrainStorm.exe
@ BrainStorm.exe votre réponse doit être marquée comme réponse. Merci.
MDP
1

L'ordre pour la résolution de surcharge de méthode est ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. La première phase effectue la résolution de surcharge sans autoriser la conversion de boxe ou de déballage, ou l'utilisation de l'invocation de méthode d'arité variable. Si aucune méthode applicable n'est trouvée pendant cette phase, le traitement se poursuit à la deuxième phase.

    Cela garantit que tous les appels valides dans le langage de programmation Java avant Java SE 5.0 ne sont pas considérés comme ambigus en raison de l'introduction de méthodes d'arité variable, du boxing implicite et / ou du déballage. Cependant, la déclaration d'une méthode d'arité variable (§8.4.1) peut changer la méthode choisie pour une expression d'invocation de méthode de méthode donnée, car une méthode d'arité variable est traitée comme une méthode d'arité fixe dans la première phase. Par exemple, déclarer m (Object ...) dans une classe qui déclare déjà m (Object) fait que m (Object) n'est plus choisi pour certaines expressions d'invocation (telles que m (null)), comme m (Object [] ) est plus spécifique.

  2. La deuxième phase effectue la résolution de surcharge tout en permettant la boxe et le déballage, mais empêche toujours l'utilisation de l'invocation de méthode d'arité variable. Si aucune méthode applicable n'est trouvée pendant cette phase, le traitement se poursuit vers la troisième phase.

    Cela garantit qu'une méthode n'est jamais choisie via l'invocation de méthode d'arité variable si elle est applicable via l'appel de méthode d'arité fixe.

  3. La troisième phase permet de combiner la surcharge avec des méthodes d'arité variable, la boxe et le déballage.

foo(null)correspond foo(Object... arg)avec arg = nulldans la première phase. arg[0] = nullserait la troisième phase, qui n'arrive jamais.

Alexey Romanov
la source