2015-09-03 1 views
0

Je me gratte la tête sur comment fonctionne cet exemple et semble imprimer correctement.Comment compile une méthode avec un varargs de type Bounded Wildcard?

public class Test { 

    static class Shape { 
     public String toString() { 
      return "Shape"; 
     } 
    } 

    static class Circle extends Shape { 
     public String toString() { 
      return "Circle"; 
     } 
    } 

    static class Square extends Shape { 
     public String toString() { 
      return "Square"; 
     } 
    } 


    public static void wildCardVarArgs(ThreadLocal<? extends Shape>... list) { 
     for (ThreadLocal<? extends Shape> s : list) { 
      System.out.println(s.get().toString()); 
     } 
} 

    public static void test() { 
     ThreadLocal<Shape> shape = new ThreadLocal<>(); 
     shape.set(new Shape()); 
     ThreadLocal<Square> square = new ThreadLocal<>(); 
     square.set(new Square()); 
     ThreadLocal<Circle> circle = new ThreadLocal<>(); 
     circle.set(new Circle()); 
     wildCardVarArgs(shape, square, circle); 
    } 
} 

des tests d'appel imprimeront:

"Shape" 
"Square" 
"Circle" 

Intuitivement cela fait sens, comme la signature de méthode est décrite comme accepter toute somme d'arguments aussi longtemps qu'ils sont de type ThreadLocal avec un type de toute extension de forme. Donc passer dans un ThreadLocal<Square> avec ThreadLocal<Circle> correspond à la facture.

Mais comment cela compile-t-il de telle sorte que le runtime puisse déterminer la surcharge correcte de la chaîne? Ma compréhension brouillée de l'effacement de type générique donne l'impression que ce type de signature de méthode ne devrait pas être possible même de compiler. J'aurais pensé que la signature de wildCardVarArgs deviendrait quelque chose comme wildCardVarArgs(ThreadLocal[]) en octet, ce qui semble être une exécution de ClassCastException. Mais plus j'y pense, plus je deviens confus.

Quelqu'un peut-il donner un sens à cela et que fait la compilation à un type de ThreadLocal de type Bounded Wildcard?

+4

effacement ou non, 's.get()' obtiendra un objet 'Square/Circle', et le' toString 'surchargé sera appelé. – ZhongYu

+0

Si vous supprimez tous les génériques du code, il suffit d'utiliser des types bruts partout, vous pouvez voir pourquoi cela fonctionne très bien au moment de l'exécution. – ZhongYu

Répondre

3

En gros, même si vous supprimez les types, voici ce que vous obtenez:

.... 
public static void wildCardVarArgs(ThreadLocal... list) { 
    for (ThreadLocal s : list) { 
     Object o = s.get(); //Compiler knows there's "at least" Object inside 
     System.out.println(o.toString()); //And all Objects have "toString" 
    } 
} 
.... 

Alors, tout ce que vous poussez dedans, la classe de base de tout, qui est l'objet, a une méthode toString, qui votre cas est annulé. Par conséquent, l'appel de la variable de type Objet n'échoue jamais et, dans votre cas, appelle la méthode overrid.


Ajouté:

Maintenant, si vous ajoutez une nouvelle méthode dans la classe de forme, voici ce qui se passe:

public static void wildCardVarArgs(ThreadLocal<? extends Shape>... list) { 
    for (ThreadLocal<? extends Shape> s : list) { 
     Shape o = s.get(); //Compiler knows there's "at least" Shape inside 
     System.out.println(o.someCustomMethod()); //And all Shapes have "someCustomMethod", does not matter if it is overriden or not 
    } 
} 

Donné ce code, le compilateur sait pour sûr, quel que ThreadLocal vous donniez à cette méthode, il contient une sorte d'une implémentation de forme. Il ne se soucie pas vraiment de ce qu'il est, cependant, il peut garantir que vous ne lui avez rien donné, mais une sorte de forme. Par conséquent, il peut vérifier à la compilation, que la forme a en effet someCustomMethod dessus. Encore une fois, il ne s'en soucie pas à ce stade si quelqu'un Overriden ou pas, il l'appelle simplement. Si elle a été remplacée - l'une est appelée. Si ce n'est pas le cas, l'original de la classe Shape est appelé.


Plus ajouté:

j'aurais pensé que la signature pour wildCardVarArgs devient quelque chose le long des lignes de wildCardVarArgs (ThreadLocal []) dans le code octet, ce qui semble être l'exécution devrait fonctionner dans une exception ClassCastException.

C'est exactement ce qui se passe (pas exactement la partie tableau, mais l'effacement).C'est, après la compilation est tout ce qui reste, essentiellement:

public static void wildCardVarArgs(ThreadLocal... list) { 
    for (ThreadLocal s : list) { 
     Object o = s.get(); 
     Shape p = (Shape)o; //Java hopes this won't fail, because compiler checked that it should not 
     System.out.println(p.someCustomMethod()); 
    } 
} 

Cependant, Java ne se soucie pas vraiment. Il espère que pendant le temps de compilation il a trouvé toutes les erreurs correctement, donc de telles erreurs pendant l'exécution ne devraient pas arriver (spoiler: elles se produisent quand même, temps à partir du temps, si on ne fait pas attention).

+0

mais si j'ajoute une autre méthode publique "' newMethod() '" à Shape (qui ne fait pas partie de la classe Object), et surcharger cela dans les enfants, je peux toujours faire s.get(). NewMethod() ' – wave

+0

I mettra à jour ma réponse dans une minute. – bezmax

+0

Merci. Jusqu'à présent, cela a du sens du point de vue du compilateur. Mais le cas que nous avons eu dans notre code de production actuel une surcharge de 'wildCardVarArgs (ThreadLocal ... liste)' et nous avons été confus sur la façon dont le compilateur distingue les deux. – wave