J'expérimente avec ce code:
interface Callee {
public void foo(Object o);
public void foo(String s);
public void foo(Integer i);
}
class CalleeImpl implements Callee
public void foo(Object o) {
logger.debug("foo(Object o)");
}
public void foo(String s) {
logger.debug("foo(\"" + s + "\")");
}
public void foo(Integer i) {
logger.debug("foo(" + i + ")");
}
}
Callee callee = new CalleeImpl();
Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();
callee.foo(i);
callee.foo(s);
callee.foo(o);
Cela imprime foo(Object o)
trois fois. Je m'attends à ce que la sélection de la méthode prenne en considération le type de paramètre réel (pas le déclaré). Suis-je en train de manquer quelque chose? Existe-t-il un moyen de modifier ce code pour qu'il affiche foo(12)
, foo("foobar")
et foo(Object o)
?
Je m'attends à ce que la sélection de la méthode prenne en considération le type de paramètre réel (pas le déclaré). Suis-je en train de manquer quelque chose?
Oui. Votre attente est fausse. En Java, la répartition dynamique des méthodes se produit uniquement pour l'objet auquel la méthode est appelée, pas pour les types de paramètres des méthodes surchargées.
Citant le Java Language Specification :
Lorsqu'une méthode est invoquée (§15.12), le nombre d'arguments réels (et tout argument de type explicite) et les types de compilation des arguments sont utilisé, lors de la compilation, pour déterminer la signature de la méthode qui sera invoquée (§15.12.2). Si la méthode à invoquer est une méthode d'instance, la méthode réelle à invoquer sera déterminée au moment de l'exécution, à l'aide de la recherche de méthode dynamique (§15.12.4).
Comme mentionné précédemment, la résolution de surcharge est effectuée au moment de la compilation.
Java Puzzlers a un bel exemple pour cela:
Puzzle 46: Le cas du constructeur déroutant
Ce puzzle vous présente deux constructeurs déroutants. La méthode principale invoque un constructeur, mais lequel? La sortie du programme dépend de la réponse. Qu'est-ce que le programme imprime ou est-il même légal?
public class Confusing {
private Confusing(Object o) {
System.out.println("Object");
}
private Confusing(double[] dArray) {
System.out.println("double array");
}
public static void main(String[] args) {
new Confusing(null);
}
}
Solution 46: Cas du constructeur déroutant
... Le processus de résolution de surcharge de Java fonctionne en deux phases. La première phase sélectionne toutes les méthodes ou constructeurs accessibles et applicables. La deuxième phase sélectionne le le plus spécifique des méthodes ou constructeurs sélectionnés dans la première phase. Une méthode ou un constructeur est moins spécifique qu'un autre s'il peut accepter n'importe quel paramètre passé à l'autre [JLS 15.12.2.5].
Dans notre programme, les deux constructeurs sont accessibles et applicables. Le constructeur Confus (Object) accepte tous les paramètres passés à Confusing (double []), donc Confusing (Object) est moins spécifique. (Chaque double tableau est un objet, mais pas tous objet est un double tableau.) Le constructeur le plus spécifique est donc Confus (double []), ce qui explique la sortie du programme.
Ce comportement est logique si vous passez une valeur de type double []; c'est contre-intuitif si vous passez null. La clé pour comprendre ce puzzle est que le test pour lequel la méthode ou le constructeur est le plus spécifique n'utilise pas les paramètres réels : les paramètres apparaissant dans l'invocation. Ils sont utilisés uniquement pour déterminer les surcharges applicables. Une fois que le compilateur détermine les surcharges applicables et accessibles, il sélectionne la surcharge la plus spécifique, en utilisant uniquement les paramètres formels: les paramètres apparaissant dans la déclaration.
Pour appeler le constructeur Confusing (Object) avec un paramètre null, écrivez new Confusing ((Object) null). Cela garantit que seul Confus (Object) est applicable. Plus généralement, pour forcer le compilateur à sélectionner une surcharge spécifique, transtypez les paramètres réels en types déclarés des paramètres formels.
La possibilité d'envoyer un appel à une méthode basée sur des types d'arguments s'appelle répartition multiple . Dans Java cela se fait avec modèle de visiteur .
Cependant, étant donné que vous traitez avec Integer
s et String
s, vous ne pouvez pas facilement intégrer ce modèle (vous ne pouvez tout simplement pas modifier ces classes). Ainsi, un géant switch
sur l'exécution de l'objet sera votre arme de choix.
Dans Java la méthode à appeler (comme dans quelle signature de méthode utiliser) est déterminée au moment de la compilation, elle va donc avec le type de temps de compilation.
Le modèle typique pour contourner ce problème consiste à vérifier le type d'objet dans la méthode avec la signature d'objet et à déléguer à la méthode avec un transtypage.
public void foo(Object o) {
if (o instanceof String) foo((String) o);
if (o instanceof Integer) foo((Integer) o);
logger.debug("foo(Object o)");
}
Si vous avez plusieurs types et que cela n'est pas gérable, alors la surcharge de méthode n'est probablement pas la bonne approche, la méthode publique devrait plutôt prendre Object et implémenter une sorte de modèle de stratégie pour déléguer la gestion appropriée par type d'objet.
J'ai eu un problème similaire avec l'appel du bon constructeur d'une classe appelée "Parameter" qui pouvait prendre plusieurs types de base Java tels que String, Integer, Boolean, Long, etc. Étant donné un tableau d'objets , Je veux les convertir en un tableau de mes objets Parameter en appelant le constructeur le plus spécifique pour chaque objet dans le tableau d'entrée. Je voulais également définir le constructeur Parameter (Object o) qui déclencherait une IllegalArgumentException. J'ai bien sûr trouvé cette méthode étant invoquée pour chaque objet de mon tableau.
La solution que j'ai utilisée était de rechercher le constructeur par réflexion ...
public Parameter[] convertObjectsToParameters(Object[] objArray) {
Parameter[] paramArray = new Parameter[objArray.length];
int i = 0;
for (Object obj : objArray) {
try {
Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
paramArray[i++] = cons.newInstance(obj);
} catch (Exception e) {
throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
}
}
return paramArray;
}
Aucune instance de laid, instructions de changement ou modèle de visiteur requis! :)
Java examine le type de référence lors de la tentative de détermination de la méthode à appeler. Si vous souhaitez forcer votre code, vous choisissez la "bonne" méthode, vous pouvez déclarer vos champs comme des instances du type spécifique:
Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();
Vous pouvez également convertir vos paramètres en tant que type de paramètre:
callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);
S'il existe une correspondance exacte entre le nombre et les types d'arguments spécifiés dans l'appel de méthode et la signature de méthode d'une méthode surchargée, c'est la méthode qui sera invoquée. Vous utilisez des références Object, donc Java décide au moment de la compilation que pour Object param, il existe une méthode qui accepte directement Object. Elle a donc appelé cette méthode 3 fois.