Est une Liste<Chien> une sous-classe de la Liste<Animal>? Pourquoi Java génériques pas implicitement polymorphe?
Je suis un peu confus sur la façon de Java génériques l'héritage de la poignée /le polymorphisme.
Assumer la hiérarchie suivante -
Animal (Parent)
Chien - Chat (Enfants)
Supposons donc que j'ai une méthode doSomething(List<Animal> animals)
. Toutes les règles d'héritage et de polymorphisme, je suppose qu'un List<Dog>
est un List<Animal>
et un List<Cat>
est un List<Animal>
- et donc, soit l'un pourrait être transmis à cette méthode. De ne pas faire. Si je veux obtenir ce comportement, je dois indiquer explicitement la méthode d'accepter une liste de toute sous-classe de l'Animal en disant doSomething(List<? extends Animal> animals)
.
Je comprends que c'est de Java comportement. Ma question est pourquoi? Pourquoi est-polymorphisme généralement implicite, mais quand il s'agit de génériques, il doit être spécifié?
les génériques comme cela se fait en Java sont une mauvaise forme de polymorphisme paramétrique. Ne mettez pas trop dans la foi en eux (comme je le faisais), parce qu'un jour vous serez frappé dur de leur pathétique limitations: le Chirurgien s'étend que ça reste soutenable<Scalpel>, que ça reste soutenable<Éponge> KABOOM! N' pas calculer [TM]. Il y a votre Java génériques de prescription. Tout OOA/OOD peut être traduit amende en Java (et MI peut être fait très bien à l'aide d'interfaces Java) mais les génériques il suffit de ne pas le couper. Ils vont bien pour les "collections" et programmation procédurale qui a dit (qui est ce que la plupart des programmeurs Java faire de toute façon...).
Super classe de Liste<Chien> n'est pas une Liste<Animal> mais<?> (j'.e liste de type inconnu) . Les génériques efface les informations de type dans le code compilé. Ceci est fait de sorte que le code qui est l'utilisation de génériques(java 5 & ci-dessus) est compatible avec les versions antérieures de java sans les génériques.
SI la question - qui est l'utilisation de dire <? s'étend SomeObject> au lieu de <SomeObject>
puisque personne ne semblait réagir... il devrait certainement être "pourquoi ne sont pas les generics de Java...". L'autre problème est que "générique" est en fait un adjectif, et ainsi de "génériques" se réfère à la perte de la nom pluriel modifié par "générique". Vous pourriez dire "que la fonction est un générique", mais qui serait plus difficile que de dire "que la fonction est générique". Cependant, il est un peu lourd à-dire "en Java générique de fonctions et de classes", au lieu de simplement "Java a les génériques". Comme quelqu'un qui a écrit leur thèse de maîtrise sur les adjectifs, je pense que vous avez trébuché sur une question très intéressante!
OriginalL'auteur froadie | 2010-04-30
Vous devez vous connecter pour publier un commentaire.
Non, un
List<Dog>
est pas unList<Animal>
. Pensez à ce que vous pouvez faire avec unList<Animal>
- vous pouvez ajouter tout animal... y compris un chat. Maintenant, pouvez-vous logiquement ajouter un chat à une portée de chiots? Absolument pas.Tout à coup vous avez un très chat confus.
Maintenant, vous ne peut pas ajouter un
Cat
à unList<? extends Animal>
parce que vous ne savez pas, c'est unList<Cat>
. Vous pouvez récupérer une valeur et de savoir qu'il va être unAnimal
, mais vous ne pouvez pas ajouter arbitraire des animaux. L'inverse est vrai pourList<? super Animal>
- dans ce cas, vous pouvez ajouter unAnimal
en toute sécurité, mais vous ne savez pas quelque chose sur ce qui peut être récupéré à partir, car il pourrait être unList<Object>
.Non, pas vraiment: vous pouvez ajouter un chat à une liste d'animaux, mais vous ne pouvez pas ajouter un chat à une liste de chiens. Une liste de chiens n'est qu'une liste d'animaux si vous considérez que c'est en lecture seul sens.
Bien sûr, mais qui exige que la fabrication d'une nouvelle liste à partir d'un chat et d'une liste de chiens de modifier la liste des chiens? C'est un arbitraire de la décision de mise en œuvre en Java. Celui qui va à l'encontre de la logique et de l'intuition.
Je n'aurais pas utilisé ce "certainement" pour commencer. Si vous avez une liste qui dit en haut "Hôtels que nous voulions aller à" et puis quelqu'un a ajouté une piscine, pensez-vous qu'il valide? N' - il une liste des hôtels, ce qui n'est pas une liste de bâtiments. Et ce n'est pas comme je l'ai même dit: "Une liste de chiens n'est pas une liste des animaux" - je l'ai mis en termes de code, dans une police de code. Je ne pense vraiment pas qu'il n'y a aucune ambiguïté ici. À l'aide de la sous-classe serait incorrect de toute façon - c'est à propos de la compatibilité d'affectation, pas de sous-classement.
Le problème, c'est que vous êtes alors en barques à fond plat au moment de l'exécution de quelque chose qui peut être bloquée au moment de la compilation. Et je dirais que la matrice de covariance a été une erreur de conception pour commencer.
OriginalL'auteur Jon Skeet
Ce que vous cherchez est appelé covariante du type paramètres. Cela signifie que si un type d'objet peut être substitué à un autre dans une méthode (par exemple,
Animal
peut être remplacé parDog
), la même chose s'applique à des expressions à l'aide de ces objets (doncList<Animal>
pourrait être remplacé parList<Dog>
). Le problème est que la covariance n'est pas sans danger pour mutable listes en général. Supposons que vous avez unList<Dog>
, et il est utilisé comme unList<Animal>
. Ce qui se passe lorsque vous essayez d'ajouter un Chat à cetteList<Animal>
qui est vraiment unList<Dog>
? Automatiquement permettant paramètres de type à être covariantes rompt le type de système.Il serait utile d'ajouter la syntaxe pour permettre le type de spécifier des paramètres comme covariants, ce qui évite la
? extends Foo
dans les déclarations de méthode, mais qui ajoute une complexité supplémentaire.List<Object>
OriginalL'auteur Michael Ekstrand
La raison pour laquelle un
List<Dog>
n'est pas unList<Animal>
, c'est que, par exemple, vous pouvez insérer unCat
dans unList<Animal>
, mais pas dans unList<Dog>
... vous pouvez utiliser des caractères génériques pour faire des génériques plus extensible lorsque cela est possible; par exemple, la lecture d'unList<Dog>
est similaire à la lecture d'unList<Animal>
-- mais pas de l'écriture.La Les génériques du Langage Java et la La Section sur les Generics de Java Tutoriels avez une très bonne, en profondeur expliquer pourquoi certaines choses sont ou ne sont pas polymorphes ou permis de génériques.
OriginalL'auteur Michael Aaron Safyan
Je dirais que le point de l'ensemble des Génériques, c'est qu'il ne permet pas que. Envisager la situation avec les tableaux, qui autorisent ce type de covariance:
Que le code compile bien, mais lève une erreur à l'exécution (
java.lang.ArrayStoreException: java.lang.Boolean
dans la deuxième ligne). Il n'est pas typesafe. Le point de Génériques est à ajouter au moment de la compilation, type de sécurité, sinon vous pouvez simplement coller avec une plaine de classe sans les génériques.Maintenant il ya des moments où vous avez besoin d'être plus flexible et qu'est ce que le
? super Class
et? extends Class
sont pour. L'ancien, c'est quand vous avez besoin d'insérer dans un typeCollection
(par exemple), et le dernier est pour quand vous avez besoin de lire, dans un type de manière sécuritaire. Mais le seul moyen de faire les deux en même temps est d'avoir un type spécifique.Pour info: j'ai parlé de votre réponse stackoverflow.com/a/26551453/295802
"je dirais que le point de l'ensemble des Génériques est qu'elle ne permet pas cela.". Vous ne pouvez jamais être sûr: Java et Scala Type de Systèmes sont Douteuses: La Crise Existentielle de Pointeurs Null (présenté à OOPSLA 2016) (depuis corrigé il me semble)
OriginalL'auteur Yishai
Un point, je pense, devrait être ajoutée à ce d'autres réponses mentionner, c'est que tout
il est vrai aussi que
La façon dont les OP de l'intuition qui est tout à fait valable, bien sûr - est de la dernière phrase. Cependant, si l'on applique cette intuition, nous obtenons une langue qui n'est pas Java-esque dans son type de système: Supposons que notre langue ne permettre l'ajout d'un chat à la liste de nos chiens. Ce que cela signifierait? Cela voudrait dire que la liste a de cesse d'être une liste de chiens, et reste simplement une liste d'animaux. Et une liste des mammifères, et une liste de quadrapeds.
Pour le dire d'une autre façon: Un
List<Dog>
en Java ne signifie pas "une liste de chiens" en anglais, cela signifie "une liste qui peut avoir des chiens, et rien d'autre".Plus généralement, OP intuition se prête vers une langue dans laquelle les opérations sur les objets peuvent changer de type de, ou plutôt, un type d'objet(s) est un (dynamique) en fonction de sa valeur.
Comme quelqu'un qui trouve les comparaisons constantes de tableaux encore plus de confusion, cette réponse a cloué pour moi. Mon problème était le langage de l'intuition.
OriginalL'auteur einpoklum
Pour comprendre le problème, il est utile de faire la comparaison avec les tableaux.
List<Dog>
est pas sous-classe deList<Animal>
.Mais
Dog[]
est sous-classe deAnimal[]
.Les tableaux sont reifiable et covariants.
Reifiable signifie que leur type d'information est entièrement disponible au moment de l'exécution.
Par conséquent, les tableaux permettent d'exécution type de la sécurité, mais pas au moment de la compilation de sécurité de type.
C'est l'inverse pour les génériques:
Les génériques sont effacé et invariant.
Par conséquent, les génériques peuvent pas fournir d'exécution type de sécurité, mais ils fournissent le type de compilation de sécurité.
Dans le code ci-dessous si les génériques ont été covariante, il sera possible de faire tas de la pollution à la ligne 3.
Les tableaux étant covariante est un compilateur "fonctionnalité".
OriginalL'auteur outdev
La base de la logique de ce comportement est que
Generics
suivre un mécanisme de type erasure. Ainsi, au moment de l'exécution, vous n'avez aucun moyen si l'identification du type decollection
contrairement àarrays
où il n'existe pas de tels processus d'effacement. Donc, pour revenir à votre question...Supposons donc que s'il existe une méthode comme indiqué ci-dessous:
Maintenant, si java permet à l'appelant d'ajouter la Liste de type de l'Animal à cette méthode, puis vous pouvez ajouter quelque chose de mal dans la collecte et au moment de l'exécution trop, il sera exécuté en raison du type d'effacement. Alors que dans le cas des tableaux, vous obtiendrez une exception pour ce type de scénarios...
Est donc essentiellement ce comportement est mis en œuvre de sorte que l'on ne peut pas ajouter quelque chose de mal dans la collection. Maintenant, je crois que le type d'effacement existe afin de garantir la compatibilité avec les anciens de java sans les génériques....
OriginalL'auteur Hitesh
Les réponses données ici n'ont pas tout à me convaincre. Donc à la place, je fais un autre exemple.
sonne bien, n'est-ce pas? Mais vous ne pouvez passer
Consumer
s etSupplier
s pourAnimal
s. Si vous avez unMammal
consommateur, mais unDuck
fournisseur, ils ne devrait pas s'adapter bien que les deux sont des animaux. Afin d'interdire ce, des restrictions supplémentaires ont été ajoutés.Au lieu de ce qui précède, nous avons à définir les relations entre les types d'utilisation.
E. g.,
permet de s'assurer que nous ne pouvons utiliser un fournisseur qui nous donne le bon type d'objet pour le consommateur.
Otoh, que, nous pourrions aussi bien faire
où nous aller dans l'autre sens: nous définissons le type de la
Supplier
et de restreindre qu'il peut être mis dans leConsumer
.Nous pouvons même faire
où, ayant l'intuition que les relations
Life
->Animal
->Mammal
->Dog
,Cat
etc., on pourrait même mettre unMammal
dans unLife
de consommation, mais pas unString
dans unLife
consommateur.(Consumer<Runnable>, Supplier<Dog>)
toutDog
est sous-type deAnimal & Runnable
groups.google.com/forum/#!topic/java-lang-fans/0GDv0salPTs
OriginalL'auteur glglgl
En fait, vous pouvez utiliser une interface pour obtenir ce que vous voulez.
}
vous pouvez ensuite utiliser les collections à l'aide
OriginalL'auteur Angel Koh
Si vous êtes sûr que les éléments de la liste sont des sous-classes de celle donnée super type, vous pouvez lancer la liste à l'aide de cette approche:
Ceci est utile lorsque vous souhaitez passer la liste dans un constructeur ou d'effectuer une itération sur elle
Si vous essayez d'ajouter un Chat à la liste, assurez-vous qu'il va créer des problèmes, mais pour la lecture en boucle, je pense que c'est la seule non détaillé réponse.
OriginalL'auteur sagits
La réponse ainsi que d'autres réponses sont correctes. Je vais ajouter à ces réponses avec une solution qui je pense sera utile. Je pense que cela vient souvent dans la programmation. Une chose à noter est que, pour les Collections (Listes, Ensembles, etc.) le principal problème est d'ajouter à la Collection. C'est là que les choses se décomposer. Même la suppression est OK.
Dans la plupart des cas, nous pouvons utiliser
Collection<? extends T>
plutôtCollection<T>
et qui devrait être le premier choix. Cependant, je viens de trouver des cas où il n'est pas facile à faire. C'est l'objet de débats quant à savoir si c'est toujours la meilleure chose à faire. Je suis présente ici une classe DownCastCollection qui peut prendre de convertir unCollection<? extends T>
à unCollection<T>
(on peut définir des catégories similaires pour la Liste, Ensemble, NavigableSet,..) pour être utilisé lors de l'utilisation de l'approche standard est très gênant. Ci-dessous est un exemple de la façon de l'utiliser (on peut aussi utiliserCollection<? extends Object>
dans ce cas, mais je suis pour garder les choses simples pour illustrer, à l'aide DownCastCollection.Maintenant, la classe:
}
Collections.unmodifiableCollection
Droite mais la collection, j'ai définissez peuvent être modifiés.
Oui, il peut être modifié.
Collection<? extends E>
déjà poignées de comportement correctement, sauf si vous l'utilisez d'une manière qui n'est pas de type sécuritaire (par exemple, le jetant à autre chose). Le seul avantage que j'y vois est, lorsque vous appelez laadd
opération, il déclenche une exception même si vous il coulé.OriginalL'auteur dan b
De sous-typage est invariant de type paramétré. Même les plus difficiles de la classe
Dog
est un sous-type deAnimal
, le type paramétréList<Dog>
n'est pas un sous-type deList<Animal>
. En revanche, covariante de sous-typage est utilisé par des tableaux, de sorte que le tableautype
Dog[]
est un sous-type deAnimal[]
.Invariante sous-typage garantit que le type de contraintes appliquées par Java ne sont pas violés. Considérons le code suivant donné par @Jon Skeet:
Comme indiqué par @Jon Skeet, ce code est illégal, parce que sinon il serait violer les contraintes de type en retournant un chat quand un chien est prévu.
Il est instructif de comparer ci-dessus à l'analogue de code pour les tableaux.
Le code est légal. Cependant, déclenche une tableau store exception.
Un tableau porte son type au moment de l'exécution de cette manière JVM peut appliquer
de sécurité de type de sous-typage covariant.
Pour comprendre cette nouvelle regardons le bytecode généré par
javap
de la classe ci-dessous:L'aide de la commande
javap -c Demonstration
, cela montre la suite de bytecode Java:Observer que la traduction du code de la méthode des corps sont identiques. Compilateur remplace chaque type paramétré par son l'effacement. Cette propriété est cruciale en ce sens qu'il n'a pas casser la compatibilité ascendante.
En conclusion, au moment de l'exécution de la sécurité n'est pas possible pour le type paramétré, depuis compilateur remplace chaque type paramétré par son effacement. Cela rend paramétrée types sont rien de plus que du sucre syntaxique.
OriginalL'auteur Root G
Permet de prendre l'exemple de JavaSE tutoriel
Alors pourquoi une liste de chiens (cercles) ne doit pas être considérée comme implicitement une liste des animaux (des formes) est à cause de cette situation:
Donc Java "architectes" en a 2 options qui traitent de ce problème:
ne considérons pas que le sous-type est implicitement c'est supertype, et de donner une erreur de compilation, comme c'est le cas actuellement
considérer le sous-type que ce soit supertype et de limiter lors de la compilation "ajouter" de la méthode (dans le drawAll méthode, si une liste de cercles, de sous-type de la forme, serait passé, le compilateur doit détecté et restreindre vous d'erreur de compilation dans le faire).
Pour des raisons évidentes, qui a choisi la première voie.
OriginalL'auteur aurelius
Il convient également de prendre en considération la façon dont le compilateur menaces les classes génériques: "instancie" un type différent à chaque fois que nous remplissons les arguments génériques.
Ainsi, nous avons
ListOfAnimal
,ListOfDog
,ListOfCat
, etc, qui sont distinctes des classes qui finissent par être "créé" par le compilateur lorsque nous spécifier les arguments génériques. Et c'est un plat de la hiérarchie (en fait, quant àList
n'est pas une hiérarchie à tous).Un autre argument pourquoi la covariance n'a pas de sens dans le cas de classes génériques, c'est le fait qu'à la base, toutes les classes sont les mêmes - sont
List
instances. Spécialisée unList
en remplissant le générique de l'argument de ne pas étendre la classe, elle permet simplement de travailler pour l'argument générique.OriginalL'auteur Cristik
Le problème a été bien identifié. Mais il y a une solution; faire doSomething générique:
maintenant, vous pouvez appeler doSomething soit avec une Liste<Chien> ou<Cat> ou<Animal>.
OriginalL'auteur gerardw
une autre solution est de créer une nouvelle liste
OriginalL'auteur ejaenv