web-dev-qa-db-fra.com

Appel de Java Méthodes génériques

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.

25

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.

9

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.

21
Aza

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é.

2
newacct