2009-07-15 9 views
7

Pourquoi les génériques java sont-ils si délicats? Je pensais que j'ai finalement compris, mais éclipse me donne une erreur à la ligne dans somOtherMethod ci-dessous en utilisant l'une des méthodes getOuterList ci-dessous.liste générique simple en java

protected List<?> getOuterList() { 
    // blah blah 
} 

protected List<? extends Object> getOuterList() { 
    // blah blah 
} 

protected void someOtherMethod() { 
    ... 
    getOuterList().add((MyObject)myObject); //compile error 
    ... 
} 

MISE À JOUR: ok - donc je comprends maintenant l'erreur. C'était un manque de compréhension de ma part de ce que signifie réellement List<?> ou List<? extends SomeObject>. Dans le premier cas, je pensais que cela signifiait une liste qui pourrait contenir n'importe quoi. Dans le dernier cas, j'ai supposé que c'était une liste d'un groupe d'objets qui étendent SomeObject. La représentation correcte de ma compréhension serait juste List<Object> et List<SomeObject> (sans les extensions). Je pensais étend m'a aidé à résoudre un problème qu'ils ne le font pas. Donc, voici où mon vrai problème réside:

public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 

    public List<Dog> getDogs() { 
    // Is there no way to handle this with generics 
    // w/out creating a new List? 
    return getGreyHounds(); //compiler error 
    } 

} 
+0

Quelle est l'erreur? – jjnguy

Répondre

1

Vous êtes trébucher sur le fait que Java génériques ne sont pas polymorphes sur le paramètre de type.

Parler à travers votre fragment de code, nous allons tirer l'exemple à part:

protected List<GreyHound> greyHounds; // List<GreyHound> is fine 

/** This method returns a lovely List of GreyHounds */ 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

/** Here is the problem. A List<GreyHound> is not a List<Dog> */ 
public List<Dog> getDogs() { 
    return getGreyHounds(); //compiler error 
} 

Donc, votre commentaire original est correct. Les deux listes sont définitivement différentes sans héritage entre elles. Donc, je suggérerais que vous étudiez ces deux options:

  1. Essayez de retourner une nouvelle liste comme vous le suggérez dans votre commentaire. Par exemple, return new ArrayList<Dog>(this.greyHounds);

  2. Avez-vous vraiment besoin de garder une liste d'une race spécifique de chien? Vous devriez peut-être définir le membre de données comme List<Dog> auquel vous ajoutez vos GreyHounds spécifiques. Par exemple, protected List<Dog> greyHoundsOnly; où vous gérez quels chiens sont autorisés dans le chenil via l'interface externe de l'objet.

Sauf si vous avez une bonne raison de garder une liste spécifique de type, je pense sérieusement à l'option 2.

EDIT: étoffant mes options proposées ci-dessus:

Option 1: Retourne une nouvelle liste. Avantages: Simple, direct, vous obtenez une liste dactylographiée et il élimine un problème de sécurité de thread (n'expose pas une référence interne au monde). Inconvénients: apparemment un coût de performance.

// Original code starts here. 
public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 
// Original code ends here 

    public List<Dog> getDogs() { 
    // This line eliminates the thread safety issue in returning 
    // an internal reference. It does use additional memory + cost 
    // CPU time required to copy the elements. Unless this list is 
    // very large, it will be hard to notice this cost. 
    return new ArrayList<Dog>(this.greyHounds); 
    } 

} 

Option 2: utiliser une représentation de données différent. Avantages: joue mieux avec le polymorphisme, renvoie la liste générique qui était l'objectif d'origine. Inconvénients: c'est une architecture légèrement différente qui peut ne pas correspondre à la tâche d'origine.

public abstract class DogKennel { 
    protected List<Dog> dogs = new ArrayList<Dog>(); 
} 

public class GreyHoundKennel extends DogKennel { 

    // Force an interface that only allows what I want to allow 
    public void addDog(GreyHound greyHound) { dogs.add(greyHound); } 

    public List<Dog> getDogs() { 
    // Greatly reduces risk of side-effecting and thread safety issues 
    // Plus, you get the generic list that you were hoping for 
    return Collections.unmodifiableList(this.dogs); 
    } 

} 
+0

option # 1: c'est un coup de performance. mieux vaut ne pas utiliser de génériques. option # 2: "Dois-je vraiment garder une liste de races spécifiques de chiens?" Non, mais je pensais que c'est ce que les listes tapées t'ont acheté? Si je ne peux pas faire cela, alors quel est le point de génériques. Ma conclusion: Generics est une perte de temps. – andersonbd1

+0

@ andersonbd1, Vous pensez honnêtement que l'option 1 est un coup de performance? Combien de chiens suivez-vous en même temps? À moins que ce soit plus d'un millier, il vous serait très difficile de mesurer le coût, surtout si vous comparez le code à la commodité de la liste bien fournie. Si vous voulez vraiment une liste tapée, cela vous la donnera (et c'est l'option que j'utilise généralement pour beaucoup de données). Si vous pensez que les génériques sont une perte de temps, vous avez tout faux. –

+0

@BobCross - option # 1 - Bien sûr, pour 90% des listes, ce n'est pas un succès, mais nous parlons ici d'un langage généraliste. Moi, en tant que développeur d'applications, devons travailler autour de la langue comme ça? Oui, je crois que les génériques sont une perte de temps (à moins bien sûr que vous écrivez des classes de type template), mais cela ouvre tout le débat statique-dynamique :-) Pour ma part, je vais revenir à l'utilisation de collections non typées et '@SuppressWarnings (" non coché ")'. – andersonbd1

0

un type générique de? signifie "un certain type spécifique, mais je ne sais pas lequel". quelque chose en utilisant un? est essentiellement en lecture seule, car vous ne pouvez pas écrire sans connaître le type réel.

3

Vous dites que la méthode renvoie un "List de type inconnu" (auquel vous ne pouvez pas ajouter, car vous ne pouvez pas garantir que l'objet que vous ajoutez est un sous-type de ce type). Vous voulez vraiment dire, un « List de quelque type que vous voulez », vous devez faire la méthode générique:

protected <T> List<T> getOuterList() { 
    // blah blah 
} 

Ok, je viens juste regardé votre mise à jour:

Tout dépend de ce que vous avez l'intention de faire avec le résultat de getDogs(). Si vous n'avez pas l'intention de pouvoir ajouter des éléments à la liste, alors getDogs() doit renvoyer le type List<? extends Dog>, puis le problème sera résolu.

Si vous souhaitez être en mesure d'ajouter des choses à lui, et par le type List<Dog> cela signifie que vous pouvez ajouter tout type de Dog à elle, logiquement cette liste ne peut pas être la même liste que greyHounds, parce que greyHounds est de type List<GreyHound> et donc Dog objets ne devraient pas y entrer.

Cela signifie que vous devez créer une nouvelle liste. En gardant bien entendu à l'esprit que toute modification de la nouvelle liste ne serait pas reflétée dans la liste originale greyHouds.

+0

Ce n'est pas très utile à moins qu'il ne retourne une liste vide (ou une liste de 'null's). –

+0

Bravo, génériques, c'est ce que ** I ** cherchait. –

3

Cette déclaration:

List<?> getOuterList() { } 

est révélateur du compilateur « Je ne sais vraiment pas quel genre de liste que je vais revenir ». Ensuite, vous exécutez essentiellement

list<dunno-what-this-is>.add((MyObject)myObject) 

Il ne peut pas ajouter à la liste MyObject de quelque chose qu'il ne sait pas de quel type il est.

Cette déclaration:

protected List<? extends Object> getOuterList() { ... } 

indique au compilateur « Ceci est une liste des choses qui sont sous-types d'objet ». Encore une fois, bien sûr, vous ne pouvez pas lancer à "MyObject", puis ajouter à une liste d'objets. Parce que tout le compilateur sait que la liste peut contenir des objets.

Vous pouvez cependant faire quelque chose comme ceci:

List<? super MyObject>.getOuterList() { ... } 

puis ajouter avec succès un MyObject. C'est parce que maintenant le compilateur sait que la liste est une liste de MyObject, ou n'importe quel supertype de MyObject, donc il peut sûrement accepter MyObject.

Edit: Quant à votre exemple DogKennel, cet extrait de code que je pense fait ce que vous voulez:

protected List<GreyHound> greyHounds; 

// We only want a List of GreyHounds here: 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

// The list returned can be a List of any type of Dog: 
public List<? extends Dog> getDogs() { 
    return getGreyHounds(); 
} 
0

Il existe déjà une réponse acceptée, cependant, veuillez considérer la modification de code suivante. Les génériques Java sont en effet cassés, mais pas si cassés. BTW Scala corrige ce problème de manière très élégante en gérant la variance.

MISE À JOUR ----------

S'il vous plaît envisager un extrait mis à jour du code.

public interface DogKennel<T extends Dog> { 
    public List<T> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel<GreyHound> { 
    private List<GreyHound> greyHounds; 

    public List<GreyHound> getDogs() { 
     return greyHounds; // no compilation error 
    } 

    public static void main(String[] args) { 
     GreyHoundKennel inst = new GreyHoundKennel(); 
     inst.getDogs().add(new GreyHound()); // no compilation error 
    } 
} 
+0

mais vous ne pouvez pas ajouter à la liste renvoyée par getDogs - c'est ce qui a déclenché cette discussion. – andersonbd1

+0

Mes excuses - je me suis concentré plus sur la deuxième partie de votre poste. SVP envisager une approche alternative dans la section UPDATE. Veuillez également noter qu'en règle générale, il vaut mieux ne pas muter directement les propriétés de collection. Au lieu de cela, il est recommandé de fournir des méthodes mutator séparées telles que addDog, removeDog et que le getter renvoie la collection non modifiable comme proposé dans la réponse acceptée. – 01es

Questions connexes