operaciones libreria funcionales funcional ejercicios ejemplo collection colecciones java java-8 java-stream collectors

libreria - ¿Por qué la clase Java 8 ''Collector'' está diseñada de esta manera?



libreria stream java (3)

2 razones relacionadas

  • Composición funcional a través de combinadores. (Tenga en cuenta que todavía puede hacer la composición de OO, pero mire el punto a continuación)
  • Posibilidad de enmarcar la lógica de negocio en un código expresivo sucinto a través de una expresión lambda o una referencia de método cuando el objetivo de asignación es una interfaz funcional .

    Composición funcional

    La API de recopiladores allana el camino para la composición funcional a través de los combinadores. Es posible crear una funcionalidad pequeña / pequeña reutilizable y combinar algunas de ellas a menudo de forma interesante en una función / función avanzada.

    sucinto código expresivo

    A continuación, utilizamos el puntero a la función (Employee :: getSalary) para completar la funcionalidad de mapper desde el objeto Employee hasta int. summingInt llena la lógica de sumar ints y por lo tanto, combinados juntos, tenemos la suma de los salarios escritos en una sola línea de código declarativo.

    // Calcular la suma de los salarios del empleado int total = employees.stream () .collect (Collectors.summingInt ( Employee :: getSalary )));

Sabemos que Java 8 introduce una nueva API Stream y java.util.stream.Collector es la interfaz para definir cómo agregar / recopilar la secuencia de datos.

Sin embargo, la interfaz de coleccionista está diseñada de esta manera:

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

¿Por qué no está diseñado como el siguiente?

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

El último es mucho más fácil de implementar. ¿Cuál fue la consideración para diseñarlo como el anterior?


En realidad, originalmente fue diseñado de manera similar a lo que propone. Vea la implementación temprana en el repositorio del proyecto lambda ( makeResult es ahora el supplier ). Más tarde se updated al diseño actual. Creo que la razón de esta actualización es simplificar los combinadores de colectores. No encontré ninguna discusión específica sobre este tema, pero mi suposición es respaldada por el hecho de que el colector de mapping apareció en el mismo conjunto de cambios. Considere la implementación de 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()); }

Esta implementación necesita redefinir solo la función del accumulator , dejando al supplier , el combiner y el finisher como están, para que no tenga indirecciones adicionales al llamar al supplier , combiner o finisher : simplemente llama directamente a las funciones devueltas por el recolector original. Es incluso más importante con la 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); }

Aquí solo se cambia el finisher , pero se utilizan el supplier original, el accumulator y el combiner . Como se necesita un accumulator para cada elemento, reducir la indirección podría ser muy importante. Intenta reescribir el mapping y la collectingAndThen con tu diseño propuesto y verás el problema. Los nuevos colectores JDK-9 como el filtering y el flatMapping también se benefician del diseño actual.


La composición se ve favorecida por la herencia.

El primer patrón en su pregunta es una especie de configuración de módulo. Las implementaciones de la interfaz de recopilador pueden proporcionar implementaciones variables para Proveedor, Acumulador, etc. Esto significa que se pueden componer implementaciones de recopilador a partir de un grupo existente de implementaciones de Proveedor, Acumulador, etc. Esto también ayuda a reutilizar y dos Recopiladores pueden usar la misma implementación de Acumulador. Stream.collect() usa los comportamientos suministrados.

En el segundo patrón, la implementación del recopilador tiene que implementar todas las funciones por sí mismo. Todos los tipos de variaciones necesitarían anular la implementación principal. No hay mucho alcance para reutilizar, más duplicación de código si dos recopiladores tienen una lógica similar para un paso, por ejemplo, la acumulación.