J'étudie Java fonctionnalité générique et je ne sais pas comment expliquer la troisième ligne dans la méthode main
suivante:
public class Example4 {
public static void main(final String[] args) {
System.out.println(Util.<String>compare("a", "b"));
System.out.println(Util.<String>compare(new String(""), new Long(1)));
System.out.println(Util.compare(new String(""), new Long(1)));
}
}
class Util {
public static <T> boolean compare(T t1, T t2) {
return t1.equals(t2);
}
}
La première ligne compile, s'exécute et renvoie (comme prévu) false
.
La deuxième ligne ne se compile pas comme prévu, car je mélange explicitement String
et Long
.
La troisième ligne compile, s'exécute et retourne false, mais je ne suis pas sûr de comprendre comment cela se passe: le compilateur/JVM instancie-t-il le type de paramètre T
comme Object
? (De plus, y aurait-il un moyen d'obtenir ce type déclaré de T
sont runtime?)
Je vous remercie.
La réponse semble aller au-delà des réponses de @Telthien et @newacct. J'étais curieux de "voir" par moi-même la différence entre:
System.out.println(Util.<String>compare("a", "b"));
avec typage explicite, et:
System.out.println(Util.compare(new String(""), new Long(1)));
avec typage implicite.
J'ai effectué plusieurs expériences, en utilisant des variations sur ces deux lignes précédentes. Ces expériences montrent que, à moins d'utiliser le astuce de classe anonyme/locale , le compilateur vérifie les types pendant la compilation mais les bytecodes générés se réfèrent uniquement à Object
, même dans le cas de la Première ligne.
Le morceau de code suivant montre que les transcriptions de type peuvent être effectuées en toute sécurité jusqu'à Object
même dans le cas de l'argument de type explicite <String>
.
public final class Example44 {
public static void main(final String[] args) {
System.out.println(new Util44<String>().compare("a", "b"));
System.out.println(new Util44().compare(new String(""), new Long(1)));
}
}
final class Util44<T> {
private T aT;
public boolean compare(T t1, T t2) {
System.out.println(this.aT);
// I was expecting the second and third assignments to fail
// with the first invocation because T is explicitly a String
// and then to work with the second invocation because I use
// a raw type and the compiler must infer a common type for T.
// Actually, all these assignments succeed with both invocation.
this.aT = (T) new String("z");
this.aT = (T) new Long(0);
this.aT = (T) new Object();
return t1.equals(t2);
}
}
Les bytecodes de la méthode main
ressemblent à:
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 7, Locals: 1
public static void main(Java.lang.String[] args);
0 getstatic Java.lang.System.out : Java.io.PrintStream [16]
3 new ca.polymtl.ptidej.generics.Java.Util44 [22]
6 dup
7 invokespecial ca.polymtl.ptidej.generics.Java.Util44() [24]
10 ldc <String "a"> [25]
12 ldc <String "b"> [27]
14 invokevirtual ca.polymtl.ptidej.generics.Java.Util44.compare(Java.lang.Object, Java.lang.Object) : boolean [29]
17 invokevirtual Java.io.PrintStream.println(boolean) : void [33]
20 getstatic Java.lang.System.out : Java.io.PrintStream [16]
23 new ca.polymtl.ptidej.generics.Java.Util44 [22]
26 dup
27 invokespecial ca.polymtl.ptidej.generics.Java.Util44() [24]
30 new Java.lang.String [39]
33 dup
34 ldc <String ""> [41]
36 invokespecial Java.lang.String(Java.lang.String) [43]
39 new Java.lang.Long [46]
42 dup
43 lconst_1
44 invokespecial Java.lang.Long(long) [48]
47 invokevirtual ca.polymtl.ptidej.generics.Java.Util44.compare(Java.lang.Object, Java.lang.Object) : boolean [29]
50 invokevirtual Java.io.PrintStream.println(boolean) : void [33]
53 return
Line numbers:
[pc: 0, line: 24]
[pc: 20, line: 25]
[pc: 53, line: 26]
Local variable table:
[pc: 0, pc: 54] local: args index: 0 type: Java.lang.String[]
Il est en fait logique que tous les appels soient toujours vers des méthodes avec Object
comme types de paramètres formels, comme expliqué dans une autre question/réponse . Pour conclure, le compilateur utilise toujours Object
pour les bytecodes générés, peu importe s'il existe un argument de type explicité (première ligne) ou un argument de type implicite mais que les objets peuvent avoir une superclasse commune différente de Object
.
Le type hérité partagé de String
et Long
est Object
.
Lorsque vous exécutez cette fonction en tant que Util.<String>compare(
, Le compilateur s'attend à trouver deux entrées de chaîne et donne une erreur dans le cas contraire. Cependant, son exécution sans <String>
Entraîne l'utilisation du type hérité partagé le plus proche - dans ce cas, Object
.
Ainsi, lorsque compare
accepte t1
Et t2
, Ils sont convertis en Object
et le code fonctionne correctement.
Pour obtenir le type réel au moment de l'exécution, vous utilisez la même technique que vous utiliseriez avec n'importe quel autre objet: la getClass()
qui est héritée de la classe Object
.
Oui, Object
est un choix pour T
qui lui permettra de compiler. Conceptuellement, le compilateur déduit un type pour T
. Ce qu'il infère particulièrement n'a pas d'importance - tant qu'il peut inférer que certains types fonctionnera pour T
, puis il compilera. Peu importe le type déduit, car il n'a aucun effet sur le code compilé.