Question Invoquer des méthodes génériques Java


J'étudie la fonctionnalité générique Java et je ne sais pas comment expliquer la troisième ligne dans ce qui suit main méthode:

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, exécute et renvoie (comme prévu) false.

La deuxième ligne ne compile pas comme prévu, car je mélange explicitement String et Long.

La troisième ligne compile, exécute et retourne false mais je ne suis pas sûr de comprendre comment cela fonctionne: le compilateur / JVM instancie-t-il le type de paramètre T comme Object? (En outre, y aurait-il un moyen d'obtenir ce type déclaré de T sont à l'exécution?)

Je vous remercie.


25
2018-04-08 08:48


origine


Réponses:


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 génère une erreur si ce n'est pas le cas. Cependant, l'exécuter sans <String> entraîne l'utilisation du type hérité partagé le plus proche - dans ce cas, Object.

Ainsi, quand compare accepte t1 et t2, ils ont été coulés comme Object, et le code fonctionne bien.

Pour obtenir le type réel à l'exécution, vous utilisez la même technique que vous utiliseriez avec tout autre objet: getClass() qui est hérité de la Object classe.


20
2018-04-08 08:52



La réponse semble aller au-delà des réponses de @Telthien et @newacct. J'étais curieux de "voir" moi-même la différence entre:

System.out.println(Util.<String>compare("a", "b"));

avec saisie explicite et:

System.out.println(Util.compare(new String(""), new Long(1)));

avec saisie implicite.

J'ai effectué plusieurs expériences, en utilisant des variations sur ces deux lignes précédentes. Ces expériences montrent que, sans utiliser le tour de classe anonyme / local, le compilateur vérifie les types lors de 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 typographies peuvent être effectuées en toute sécurité jusqu'à Object même dans le cas de l'argument de type explicité <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 du main méthode ressemble à:

  // 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 aux 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 super-classe commune différente de Object.


9
2018-04-15 07:54



Oui, Object est un choix pour T cela lui permettra de compiler. Conceptuellement, le compilateur en déduit un type pour T. Ce que cela implique en particulier n'a pas d'importance - tant qu'il peut déduire que un type travaillera pour T, alors il compile. Peu importe le type inféré, cela n'a aucun effet sur le code compilé.


2
2018-04-08 20:27