34 votes

Pourquoi la classe Java 8 "Collector" est-elle conçue de cette manière?

Nous savons que Java 8 introduit une nouvelle API de flux et java.util.stream.Collector est l'interface permettant de définir comment agréger / collecter le flux de données.

Cependant, l'interface Collector est conçue comme ceci:

 public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
}
 

Pourquoi n'est-il pas conçu comme suit?

 public interface Collector<T, A, R> {
    A supply();
    void accumulate(A accumulator, T value);
    A combine(A left, A right);
    R finish(A accumulator);
}
 

Ce dernier est beaucoup plus facile à mettre en œuvre. Quelles étaient les considérations pour le concevoir comme l'ancien?

26voto

Tagir Valeev Points 14218

En réalité, il était à l'origine conçu de façon similaire à ce que vous proposez. Voir le début de la mise en œuvre du projet lambda référentiel (makeResult est désormais supplier). Il plus tard a été mis à jour à la conception actuelle. Je crois, la raison de cette mise à jour est de simplifier collecteur combinators. Je n'ai trouvé aucune discussion spécifique sur ce sujet, mais ma conjecture est confirmée par le fait qu' mapping collector paru dans le même ensemble de modifications. Envisager la mise en œuvre de l' Collectors.mapping:

public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                           Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                               (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                               downstream.combiner(), downstream.finisher(),
                               downstream.characteristics());
}

Cette mise en œuvre doit redéfinir accumulator fonctionner que, de quitter supplier, combiner et finisher comme l'est, de sorte que vous n'avez pas d'indirection supplémentaire lors de l'appel d' supplier, combiner ou finisher: vous suffit d'appeler directement les fonctions retourné par le collecteur d'origine. Il est encore plus important avec collectingAndThen:

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                            Function<R,RR> finisher) {
    // ... some characteristics transformations ...
    return new CollectorImpl<>(downstream.supplier(),
                               downstream.accumulator(),
                               downstream.combiner(),
                               downstream.finisher().andThen(finisher),
                               characteristics);
}

Ici, seulement finisher est changé, mais originale supplier, accumulator et combiner sont utilisés. En tant que accumulator est appelée pour chaque élément, la réduction de l'indirection pourrait être assez important. Essayer de réécrire mapping et collectingAndThen avec votre proposition de conception et vous verrez le problème. Nouveau JDK-9 collectionneurs comme filtering et flatMapping également profiter de l'actuelle conception.

18voto

S.D. Points 12816

La Composition est favorisée par rapport à l'héritage.

Le premier modèle dans votre question est une sorte de configuration du module. La mise en œuvre du Collecteur d'interface peut fournir différentes implémentations de Fournisseur, Accumulateur, etc. Cela signifie que l'on peut composer Collecteur implémentations d'une piscine existante du Fournisseur, Accumulateur, etc. les implémentations. Cela permet aussi de réutiliser et de deux Collecteurs peuvent utiliser la même Accumulateur de mise en œuvre. L' Stream.collect() utilise la fourniture des comportements.

Dans le deuxième modèle, le Collecteur de mise en œuvre de toutes les fonctions par lui-même. Toutes sortes de variations aurait besoin prépondérant de la mère de mise en œuvre. Pas beaucoup de portée à ré-utiliser, en plus de la duplication de code si deux collectionneurs ont la même logique pour une étape, par exemple, de l'accumulation.

0voto

user5931 Points 11

2 raisons liées à la

  • Fonctionnelle composition via combinators. (Notez que vous pouvez encore faire OO composition mais regardez ci-dessous le point)
  • Possibilité d'encadrement de la logique métier dans succincte expressive code via lambda expression ou de la méthode de référence lors de l'attribution cible est une interface fonctionnelle.

    Fonctionnelle de la composition

    Les collectionneurs de l'API ouvre un chemin pour la fonctionnelle de la composition par combinators .j'.e. construire petit/plus petite des fonctionnalités réutilisables et de combiner certaines de ces souvent dans une voie intéressante dans une fonctionnalité avancée de la fonction/.

    succincte expressive code

    Ci-dessous, nous sommes en utilisant le pointeur de la fonction de l'Employé (:: getSalary) pour remplir la fonctionnalité de mappeur de l'Employé de l'objet de type int. summingInt remplit la logique de l'ajout de services de renseignements et, partant, combinés ensemble, nous avons somme des salaires écrit une seule ligne de code déclaratif.

    // Calcul de la somme des salaires des employés int total = employés.stream() .recueillir des(Collectionneurs.summingInt(Employé::getSalary)));

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