241 votes

Quand les génériques de Java nécessitent <? extends="" t=""?> au lieu de <T>et y at-il aucun inconvénient de commutation ?</T>

Compte tenu de l'exemple suivant (en utilisant JUnit avec Hamcrest de rapprochement)

    Map<String, Class<? extends Serializable>> expected = null;
    Map<String, Class<java.util.Date>> result = null;
    assertThat(result, is(expected));

Ce n'est pas compilé avec JUnit AssertThat signature de la méthode:

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:

public static <T> void assertThat(T result, Matcher<? extends T> matcher)

Ensuite, la compilation fonctionne.

Donc trois questions:

  1. 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.
  2. Est-il un inconvénient dans l'évolution de la assertThat méthode pour Matcher<? extends T>? Il existe d'autres cas de rupture si vous l'avez fait?
  3. Il y a tout point à la genericizing de la assertThat méthode dans JUnit? Le Comparateur de 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 le Matcher 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());
	}
}

169voto

Scott Stanchfield Points 15863

D'abord - j'ai à vous diriger vers http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - elle a fait un travail incroyable.

L'idée de base est que vous utilisez

<T extends SomeClass>

lorsque le paramètre peut être SomeClass ou de tout sous-type.

Dans votre exemple

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));

Vous êtes en train de dire que "prévu" peut contenir des objets de la Classe, qui représentent toute classe qui implémente Serializable. Votre résultat de carte dit qu'il ne peut contenir la Date d'objets de la classe.

Quand vous passez par conséquent, vous êtes paramètre T pour exactement la Carte de String->Date d'objets de la classe, ce qui ne correspond pas à la Carte String -> tout ce qui est Sérialisable.

Une chose à vérifier -- êtes-vous sûr que vous voulez de Classe<Date> et non pas la Date? Une carte de la Chaîne->Classe<Date> n'est pas son très utile en général (tout ce qu'il peut contenir est Date.class comme les valeurs plutôt que sur les instances de Date)

Comme pour genericizing assertThat, l'idée est que la méthode peut s'assurer qu'un Comparateur qui s'adapte le type de résultat est passée.

32voto

Yishai Points 42417

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 va en bénéficier.

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 classe paramétrée (<Date> ou d'une Carte<K, V> comme dans l'exemple) est de forcer un baissés 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:

List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serilizable s = new String();
addGeneric(s, dateList);

....
private <T> void addGeneric(T element, List<T> list) {
    list.add(element);
}

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>> Il 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:

private <T> void genericAdd(T value, List<T> list)

Veut être en mesure de faire les deux:

T x = list.get(0);

et

list.add(value);

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,

Matcher<? extends T>

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 correspondre.

EDIT (après plus de la contemplation et de l'expérience):

Un des gros problèmes avec la 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 fonctionne à l' 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 à partir de ci-dessus.

16voto

GreenieMeanie Points 1652

Il se résume à:

Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date

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:

List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile

...Cependant, si le type de la Liste devient ? s'étend T au lieu de T....

List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile

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 est).

9voto

erickson Points 127945

La raison de votre origine code ne se compile pas, c'est qu' <? extends Serializable> ne pas 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>() de expected. Si le compilateur a permis le code à la compilation, l' assertThat() serait sans doute pause, car il pourrait s'attendre à Date objets au lieu de l' Long objets qu'il trouve dans la carte.

7voto

GreenieMeanie Points 1652

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).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X