Quand dois-Java génériques nécessitent <? s'étend T> au lieu de <T>, et est-il un inconvénient de commutation?
Donné l'exemple suivant (en utilisant JUnit avec Hamcrest les allumettes):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
Cela ne compile pas avec JUnit assertThat
signature de la méthode de:
public static <T> void assertThat(T actual, Matcher<T> matcher)
Le compilateur message d'erreur est:
Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
<? extends java.io.Serializable>>>)
Cependant, si je change le assertThat
signature de la méthode d':
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
Alors la compilation fonctionne.
Donc trois questions:
- Exactement pourquoi ne pas la version actuelle de la compilation? Bien que j'ai vaguement comprendre la covariance des questions ici, je ne peux pas l'expliquer si je devais le faire.
- Est-il un inconvénient dans l'évolution de la
assertThat
méthode pourMatcher<? extends T>
? Il existe d'autres cas de rupture si vous l'avez fait? - Il y a tout point à la genericizing de la
assertThat
méthode dans JUnit? LeMatcher
classe ne semble pas à l'exiger, depuis JUnit appelle les matches de la méthode, qui n'est pas typé avec un médicament générique, et ressemble à une tentative de forcer un type de la sécurité qui permet de ne pas faire n'importe quoi, comme leMatcher
sera tout simplement pas en fait de match, et que le test échoue, peu importe. Pas d'activités dangereuses impliquées (ou ce qu'il paraît).
Pour référence, voici la JUnit mise en œuvre de assertThat
:
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
- ce lien est très utile (génériques, d'héritage et de sous-type): docs.oracle.com/javase/tutorial/java/generics/inheritance.html
Vous devez vous connecter pour publier un commentaire.
Première - j'ai à vous diriger vers http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- elle fait un travail incroyable.
L'idée de base est que vous utilisez
lorsque le paramètre peut être
SomeClass
ou de tout sous-type de il.Dans votre exemple,
Vous êtes en train de dire que
expected
peut contenir des objets de la Classe, qui représentent toute classe qui implémenteSerializable
. Votre résultat de carte dit qu'il ne peut contenirDate
les objets de la classe.Quand vous passez par conséquent, la configuration de
T
exactementMap
deString
àDate
les objets de la classe, qui ne correspond pasMap
deString
pour tout ce qui estSerializable
.Une chose à vérifier -- êtes-vous sûr que vous voulez
Class<Date>
et pasDate
? Une carte deString
àClass<Date>
ne sonne pas très utile en général (tout ce qu'il peut contenir estDate.class
comme des valeurs plutôt que des instances deDate
)Comme pour genericizing
assertThat
, l'idée est que la méthode peut s'assurer qu'unMatcher
qui s'adapte le type de résultat est passée.Merci à tous ceux qui ont répondu à la question, il a vraiment aidé à clarifier les choses pour moi. À la fin de Scott Stanchfield de réponse obtenu le plus proche de la façon dont j'ai fini par le comprendre, mais puisque je n'ai pas le comprendre quand il a commencé à l'écrire, je suis en train de reformuler le problème, dans l'espoir que quelqu'un d'autre en profitera.
Je vais reformuler la question en termes de Liste, car il n'a qu'un seul paramètre générique et qui sera plus facile à comprendre.
Le but de la paramétrée de classe (comme la Liste
<Date>
ou Carte<K, V>
comme dans l'exemple) est à force un triste et que le compilateur garantir que c'est sans danger (pas des exceptions d'exécution).Prenons le cas de la Liste. L'essence de ma question est pourquoi une méthode qui prend un type T et une Liste de ne pas accepter une Liste de quelque chose de plus bas dans la chaîne de l'héritage que T. Considérons cet exemple artificiel:
Ce ne sera pas compiler, parce que le paramètre de liste est une liste de dates, pas une liste de chaînes de caractères. Les génériques ne serait pas très utile si ce n'compiler.
La même chose s'applique à une Carte
<String, Class<? extends Serializable>>
Ce n'est pas la même chose qu'une Carte<String, Class<java.util.Date>>
. Ils ne sont pas covariants, donc si je voulais prendre une valeur à partir de la carte contenant la date de classes et de le mettre dans la carte contenant serializable éléments, c'est bien, mais une signature de méthode qui dit:Veut être en mesure de faire les deux:
et
Dans ce cas, même si la junit méthode ne fait pas de soins au sujet de ces choses, la signature de la méthode nécessite la covariance, qui elle n'est pas arriver, donc il ne compile pas.
Sur la deuxième question,
Aurait l'inconvénient de vraiment accepter quoi que ce soit, lorsque T est un Objet, qui n'est pas l'Api intention. L'intention est de manière statique s'assurer que le matcher correspond à l'objet réel, et il n'y a aucun moyen de les exclure de l'Objet à partir de ce calcul.
La réponse à la troisième question, c'est que rien ne serait perdu, en termes de fonctionnalité désactivée (il n'y aurait pas dangereux typecasting au sein de la JUnit API si cette méthode n'a pas été généricisés), mais ils sont en train d'essayer d'accomplir quelque chose d'autre, statique s'assurer que les deux paramètres sont susceptibles de match.
EDIT (après plus de la contemplation et de l'expérience):
L'un des plus gros problèmes avec le assertThat signature de la méthode est de tentatives d'assimiler une variable T avec un paramètre générique de T. cela ne fonctionne pas, parce qu'ils ne sont pas covariants. Ainsi, par exemple, vous pouvez avoir un T qui est un
List<String>
mais puis de passer un match que le compilateur travaille àMatcher<ArrayList<T>>
. Maintenant, si ce n'était pas un paramètre de type, ce serait bien, parce que la Liste et liste de tableaux sont covariants, mais puisque les Génériques, aussi loin que le compilateur est concerné besoin ArrayList, il ne peut pas tolérer une Liste pour des raisons que j'espère clair.List<Date>
à partir d'une méthode de typeList<Object>
? Qui devrait être à l'abri droit, même si c'est pas autorisé par java.Ça se résume à:
Vous pouvez voir la référence de Classe c1 pourrait contenir une Longue instance (depuis l'objet sous-jacent à un moment donné pourrait avoir été
List<Long>
), mais, évidemment, ne peut pas être lancé à une Date car il n'y a aucune garantie que "l'inconnu" de classe a Date. Il n'est pas typsesafe, de sorte que le compilateur ne l'autorise pas.Cependant, si nous introduisons un autre objet, disons Liste (dans votre exemple, cet objet est Matcher), alors celui-ci devient vrai:
...Cependant, si le type de la Liste devient ? s'étend T au lieu de T....
Je pense qu'en changeant
Matcher<T> to Matcher<? extends T>
, vous êtes essentiellement l'introduction du scénario similaire à l'attribution l1 = l2;C'est encore très confus d'avoir des caractères génériques imbriqués, mais j'espère que ça a du sens pour expliquer pourquoi il est utile de comprendre les génériques en regardant comment vous pouvez attribuer des références génériques les uns aux autres. C'est aussi plus de confusion puisque le compilateur est d'inférer le type de T lorsque vous faites l'appel de la fonction (vous n'êtes pas explicitement dire qu'il était T).
La raison de votre origine code ne compile pas, c'est que
<? extends Serializable>
ne pas veux dire, "toute classe qui étend la classe Sérialisable," mais ", des inconnus, mais de classe qui étend la classe Sérialisable."Par exemple, dans le code écrit, il est parfaitement valable pour attribuer
new TreeMap<String, Long.class>()>
àexpected
. Si le compilateur a permis le code à compiler, leassertThat()
ne serait sans doute pause, car il pourrait s'attendre àDate
objets au lieu de lesLong
les objets qu'il trouve dans la carte.<Serializable>
Une façon pour moi de comprendre les caractères génériques, c'est de penser que le générique n'est pas de spécifier le type de la possible des objets que, compte tenu de référence générique peut "avoir", mais le type de d'autres références génériques qu'il est compatible avec (ce qui peut sembler déroutant...) en tant Que telle, la première réponse est très trompeur en ce qu'il est libellé.
En d'autres termes,
List<? extends Serializable>
signifie que vous pouvez attribuer que la référence à d'autres Listes dont le type est un certain type inconnu qui est ou une sous-classe Sérialisable. NE PAS penser en termes d'UNE LISTE UNIQUE d'être en mesure de tenir les sous-classes de Serializable (parce que c'est incorrect de la sémantique et conduit à une incompréhension de Génériques).<? extends T>
compiler?Je sais que c'est une vieille question, mais je veux donner un exemple qui, je pense, explique délimitée jokers assez bien.
java.util.Collections
propose cette méthode:Si nous avons une Liste de
T
, la Liste peut, bien sûr, contenir des instances de types qui s'étendentT
. Si la Liste contient des Animaux, la Liste peut contenir à la fois les Chiens et les Chats (Animaux). Les chiens ont une propriété "woofVolume" et les Chats ont une propriété "meowVolume." Alors que nous le voudrions pour trier sur la base de ces propriétés particulières pour les sous-classes deT
, comment pouvons-nous attendre de cette méthode pour le faire? Une limitation de Comparateur est qu'il peut seulement comparer deux choses d'un seul type (T
). Donc, nécessitant simplement unComparator<T>
rendrait cette méthode utilisable. Mais, le créateur de cette méthode a reconnu que si quelque chose est unT
, il est aussi une instance de la super-classes deT
. Par conséquent, il nous permet d'utiliser un Comparateur deT
ou de tout super-classe deT
, c'est à dire? super T
.que si vous utilisez