2015-03-04 2 views
22

Voici un extrait de code:Pourquoi est-il possible de récupérer un objet de type "incorrect" à partir de la liste paramétrée en Java?

import java.util.*; 
class Test 
{ 
    public static void main(String[] args) 
    { 
     List<Integer> list = new ArrayList<>(); 
     addToList(list); 
     Integer i = list.get(0); //#1 fails at run-time 
     String s = list.get(0); //#2 fails at compile-time 
     list.get(0); //#3 works fine 
     System.out.println(list.get(0)); //#4 works fine, prints "string" 
    } 
    static void addToList(List list){ 
     list.add("string"); 
    } 
} 

Je comprends pourquoi est-il possible d'insérer un objet de la classe String liste paramétrisé.

Il semble que je comprenne pourquoi le code marqué #1 et #2 échoue. Mais pourquoi #3 et #4 fonctionnent? Pour autant que je comprenne, le compilateur ajoute des moulages appropriés après l'effacement de type, donc quand j'appelle list.get(0), cette méthode devrait renvoyer un objet précédemment moulé en entier. Alors pourquoi il n'y a aucune exception ClassCastException à # 3 et # 4 à l'exécution?

+1

Parce que toutes vos utilisations de 'List' n'ont pas été paramétrées :) –

Répondre

23

Le # 3 fonctionne parce que l'objet retourné par get(int) est ignoré. Tout ce qui est stocké à la position 0 est renvoyé, mais comme il n'y a pas de conversion, aucune erreur ne se produit.

Le # 4 fonctionne bien pour la même raison: l'objet produit par get(0) est traité comme java.lang.Object sous-classe dans println, parce que toString est appelé. Puisque toString() est disponible pour tous les objets Java, l'appel se termine sans erreur.

+0

Vous voulez dire que la conversion en entier n'a lieu que si j'affecte le résultat de get (0) à une variable? – fromSPb

+2

@fromSPb Oui. Il n'y a pas de conversion forcée vers 'Integer' sauf si vous affectez la valeur à quelque chose qui a besoin d'un cast. – dasblinkenlight

+10

+1. La JLS ne précise pas que le "casting", en soi, a lieu; à la place, il spécifie la conversion d'affectation ([§5.2] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.2)) et la conversion d'invocation de méthode ([ §5.3] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.3)) peut lever des ClassCastExceptions si le type d'exécution de l'instance ne correspond pas à la cible type. Les # 3 et # 4 de l'OP ne déclenchent aucune de ces règles, donc il n'y a pas d'exception ClassCastException. – ruakh

1

Les moulages sont appliqués au type de retour get, et non au add qui introduit la pollution. Sinon, vous obtiendrez une erreur de compilation ou une exception à ce stade, car vous ne pouvez pas générer un String en Integer.

+1

Désolé, mais vous semblez avoir mal compris la question. – ruakh

+0

(Soit dit en passant, je vais rétracter ma réponse, ou je peux la supprimer, si vous pensez que les autres réponses sont suffisantes.) – ruakh

9

D'abord, la raison pour laquelle vous pouvez ajouter une chaîne à un List<Integer>. Dans la méthode

static void addToList(List list){ 

vous utilisez un type brut . Les types bruts existent uniquement pour la compatibilité avec les anciennes versions de Java et ne doivent pas être utilisés dans un nouveau code. Dans la méthode addToList, le compilateur Java ne sait pas que list ne doit contenir que des entiers, et par conséquent il ne se plaint pas quand une chaîne y est ajoutée.

En ce qui concerne le comportement différent de vos deux déclarations. Integer i = list.get(0) n'échoue pas au moment de la compilation, car Java pense que list contient seulement Integer s. Seulement à l'exécution, il s'avère que le premier élément de list n'est pas un nombre entier, et donc vous obtenez un ClassCastException.

String s = list.get(0) échoue au moment de la compilation car le compilateur Java suppose que list ne contient que Entiers, et il suppose que vous essayez d'attribuer un nombre entier à une référence de chaîne.

Just list.get(0) ne stocke pas le résultat de l'appel de méthode. Donc, ni au moment de la compilation ni au moment de l'exécution, il n'y a aucune raison pour un échec.

Enfin, System.out.println(list.get(0)) travail parce System.out est un PrintStream et a une méthode println(Object), qui peut être appelé avec un argument Integer. 4: La surcharge System.out.println (Object) est appelée, étant donné que Integer < = _T Object (lire: Integer is-a Object)

3

4: La surcharge System.out.println (Object) est appelée. Notez cette liste.get (int) renvoie un Object en cours d'exécution car le paramètre type est effacé. Maintenant, lisez

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

qui vous indique « type d'insertion jette si nécessaire pour préserver la sécurité de type. », Car une fonte de type n'est pas nécessaire d'un objet à l'ClassCastException ne peut en résulter.

Pour la même raison, il n'y a pas de type cast en 3, mais les effets secondaires de l'appel de méthode peuvent survenir dont List.get n'en a pas.

6

Si vous regardez la méthode ArrayList#get. Il est le suivant:

public E get(int index) { 
    //body 
} 

Mais lors de l'exécution, il est en fait:

public Object get(int index) { 
     //body 
} 

Alors, quand vous faites Integer i = list.get(0); Le compilateur convertit:

Integer i = (Integer)list.get(0); 

maintenant à l'exécution, list.get(0) retours un type Object (qui est en fait String). Il essaie maintenant de convertir String => Integer et il échoue.

Parce qu'il est juste:

list.get(0) 

Le compilateur n'ajoute transtypage à quoi que ce soit. Donc, c'est juste, list.get(0).

System.out.println(list.get(0)); 

list.get(0) renvoie un type Object. Donc, la méthode public void println(Object x) du PrintStream est appelée.

Rappelez-vous que println(Object x) est appelé et non println(String x).