web-dev-qa-db-fra.com

Appeler Java méthode varargs avec un seul argument nul?

Si j'ai un vararg Java foo(Object ...arg) et que j'appelle foo(null, null), j'ai à la fois arg[0] Et arg[1] as nulls. Mais si j'appelle foo(null), arg lui-même est nul. Pourquoi cela se produit-il?

Comment dois-je appeler foo de telle sorte que foo.length == 1 && foo[0] == null Soit true?

88
pathikrit

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 null ou d'un tableau d'objets null. Pour un seul argument, il suppose ce dernier.

Vous avez deux choix. Convertissez explicitement le 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));
   }
}

Sortie:

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

Vous avez besoin d'un cast explicite en Object:

foo((Object) null);

Sinon, l'argument est supposé être l'ensemble du tableau que les varargs représentent.

22
Bozho

Un cas de test pour illustrer cela:

Le 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 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écution de ce test en tant que test JUnit donne:

 sendNothing (): 'x' reçu 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 (): 'x' reçu est un tableau de taille 3 

Pour rendre cela plus intéressant, appelons la fonction receive() 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 ce test en tant que test JUnit donne les résultats suivants, avec la différence Java surlignée en gras.

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

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 le null ambigu par lui-même, il suppose que le null est un Object[]. La conversion de null en Object corrigera ce problème.

3
ColinD

Je préfère

foo(new Object[0]);

pour éviter les exceptions de pointeur Null.

J'espère que ça aide.

1
Deepak Garg

L'ordre de 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 une résolution de surcharge sans autoriser la conversion de boxing ou unboxing, ou l'utilisation de l'invocation de la méthode d'arité variable. Si aucune méthode applicable n'est trouvée pendant cette phase, le traitement se poursuit jusqu'à la deuxième phase.

    Cela garantit que tous les appels qui étaient 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, boxing et/ou unboxing implicites. 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 donnée, car une méthode d'arité variable est traitée comme une méthode d'arité fixe dans la première Par exemple, déclarer m(Object...) dans une classe qui déclare déjà m(Object) provoque m(Object) pour ne plus être choisi pour certaines expressions d'invocation (comme m (null)), car m (Object []) est plus spécifique.

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

    Cela garantit qu'une méthode n'est jamais choisie via l'invocation de la méthode d'arité variable si elle est applicable via l'invocation de la 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 = null dans la première phase. arg[0] = null serait la troisième phase, qui ne se produit jamais.

1
Alexey Romanov