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 null
s. 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
?
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]
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.
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
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.
Je préfère
foo(new Object[0]);
pour éviter les exceptions de pointeur Null.
J'espère que ça aide.
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 ):
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.
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.
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.