Pourquoi Java Collector.toList () requirejs-il un espace réservé de type générique dans son type de retour?

J’ai manipulé Java Streams et, bien entendu, il n’aime pas mon code et refuse de fournir des messages d’erreur utiles. (Pour référence, je n’ai aucun problème avec C # et Linq, je comprends donc conceptuellement tout ce que j’essaie de faire.) J’ai donc commencé à creuser pour append des types génériques explicites à chaque méthode de mon code afin de pouvoir trouver la source le problème, comme l’expérience passée me dit que c’est une voie à suivre fructueuse.

En regardant autour de moi, j’ai rencontré quelque chose que je ne comprends pas. Considérons le code suivant de la source Java (reformaté un peu):

public static  Collector<T, ?, List> toList() { return new Collectors.CollectorImpl( (Supplier<List>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, Collectors.CH_ID ); } 

Pourquoi la signature de la méthode toList nécessite-t-elle un caractère générique ? dans son type de retour? Quand je le retire, je reçois

 Wrong number of type arguments: 2; required: 3 

et

 Incompatible types. Required: Collector<T, List, > Found: CollectorImpl<java.lang.Object, List, java.lang.Object> 

Quand je change ? à Object , je reçois (en se référant à ces lignes / méthodes dans le code ci-dessus):

 List::add – Cannot resolve method 'add' left.addAll – Cannot resolve method 'addAll(java.lang.Object)' 

Lorsque je remets le caractère générique et examine ces deux éléments, ils sont:

 List – public abstract boolean add(T e) List – public abstract boolean addAll(Collection c) 

De plus, je n’ai rien appris de plus.

Je comprends que dans un scénario tel que le ? extends T ? extends T , le caractère générique en Java peut être traduit en C # sous la forme d’un nouvel argument de type générique avec la valeur where TWildCard : T Mais que se passe-t-il avec toList ci-dessus, où le type de retour a un caractère générique nu?

Les collecteurs ont trois parameters de type :

T – le type d’éléments d’entrée dans l’opération de réduction

A – le type d’accumulation modifiable de l’opération de réduction (souvent masqué en tant que détail d’implémentation)

R – le type de résultat de l’opération de réduction

Pour certains collectionneurs, tels que toList , les types de A et R sont identiques, car le résultat lui-même est utilisé pour l’accumulation.

Le type réel du collecteur renvoyé par toList serait Collector, List> .

(Un exemple de collecteur qui s’accumule avec un type différent de celui obtenu est Collectors.joining() qui utilise un SsortingngBuilder .)

L’argument type de A est un caractère générique la plupart du temps, car nous ne nous soucions généralement pas de ce que c’est réellement. Son type actuel n’est utilisé qu’en interne par le collecteur, et nous pouvons le capturer si nous devons le référencer par un nom :

 // Example of using a collector. // (No reason to actually write this code, of course.) public static  collect(Stream stream, Collector c) { return captureAndCollect(stream, c); } private static  captureAndCollect(Stream stream, Collector c) { // Create a new A, whatever that is. A a = c.supplier().get(); // Pass the A to the accumulator along with each element. stream.forEach(elem -> c.accumulator().accept(a, elem)); // (We might use combiner() for eg parallel collection.) // Pass the A to the finisher, which turns it in to a result. return c.finisher().apply(a); } 

Vous pouvez également voir dans le code de toList qu’il spécifie Collectors.CH_ID comme caractéristiques, ce qui spécifie une fin d’identité. Cela signifie que son finisseur ne fait rien sauf restituer tout ce qui lui est transmis.


(Cette section est référencée par mon commentaire ci-dessous.)

Voici quelques modèles alternatifs au transport d’un paramètre de type pour l’accumulateur. Je pense que ceux-ci illustrent pourquoi la conception actuelle de la classe Collector est bonne.

  1. Utilisez juste Object , mais nous finissons par beaucoup lancer.

     interface Collector { Supplier supplier(); BiConsumer accumulator(); BiFunction combiner(); Function finisher(); } static  Collector> toList() { return Collector.of( ArrayList::new, (obj, elem) -> ((List) obj).add(elem), (a, b) -> { ((List) a).addAll((List) b); return a; }, obj -> (List) obj); } 
  2. Cachez l’accumulateur en tant que détail d’implémentation du Collector , car le Collector lui-même effectue l’accumulation en interne. Je pense que cela pourrait avoir un sens, mais c’est moins flexible et l’étape de combinaison devient plus compliquée.

     interface Collector { void accumulate(T elem); void combine(Collector that); R finish(); } static  Collector> toList() { return new Collector>() { private List list = new ArrayList<>(); @Override public void accumulate(T elem) { list.add(elem); } @Override public void combine(Collector> that) { // We could elide calling finish() // by using instanceof and casting. list.addAll(that.finish()); } @Override public List finish() { return new ArrayList<>(list); } }; }