streams procesamiento print parte metodos ejemplo datos con collection java java-8 java-stream

java - procesamiento - ¿Está permitido/aconsejable reutilizar un Coleccionista?



stream java 8 ejemplo (6)

Tengo muchos puntos en mi código que hacen:

someStream.collect(Collectors.toList())

donde Collectors.toList() crea un nuevo recopilador en cada uso.

Esto me lleva a la pregunta si está permitido y es aconsejable hacer algo como:

private final static Collector<…> TO_LIST = Collectors.toList()

para cada tipo que uso y luego uso ese único colector como:

someStream.collect(TO_LIST)

cuando se requiere un coleccionista

Como los recopiladores son apátridas y solo una colección de funciones y características, creo que debería funcionar, pero OTOH, Collectors.toList() crea un nuevo CollectorImpl<> en cada llamada.

¿Cuáles son los inconvenientes de reutilizar un recopilador?


Creo que esto es más una cuestión de estilo , pero vamos a dar algunos pensamientos:

  • Parece ser una práctica común no usar un objeto colector CONST. En ese sentido: hacerlo puede sorprender a algunos lectores, y sorprender a los lectores rara vez es algo bueno que hacer.
  • Entonces: pocos códigos pueden simplemente "copiarse" (y probablemente no deberían evitar la duplicación del código); pero aún así: señalar un objeto coleccionista distinto puede hacer que sea un poco más difícil volver a factorizar o reutilizar las construcciones de la secuencia.
  • Más allá de eso: lo dijiste tú mismo; la reutilización del recopilador depende de una implementación sin estado . Entonces te haces dependiente de que cualquier implementación sea apátrida. Probablemente no sea un problema; pero tal vez un riesgo a tener en cuenta!
  • Probablemente más importante: en la superficie, su idea parece un buen medio para la optimización . Pero bueno; cuando te preocupes por los "efectos de rendimiento" del uso de transmisiones, ¡esa creación de un solo objeto del recopilador final "no lo cortará"!

Lo que quiero decir con eso: si estás preocupado por "malgastar" el rendimiento; preferiría buscar en cada línea de código que usa flujos para determinar si esa corriente está trabajando con objetos "suficientes" para justificar el uso de flujos en primer lugar. Esas secuencias vienen con bastante sobrecarga!

Para resumir: la comunidad java todavía tiene que encontrar "mejores prácticas estándar" para las transmisiones; por lo tanto, mi (dos) (personal) centavos en este momento: prefiera los patrones que "todo el mundo" está usando, evite hacer lo suyo. Especialmente cuando está "relacionado con el rendimiento".


Dado que el Collector es básicamente un contenedor para las cuatro funciones y banderas de características, no hay problema para reutilizarlo, pero raramente tiene alguna ventaja, ya que el impacto de un objeto tan liviano en la administración de la memoria es insignificante, si no lo elimina completamente el optimizador. de todas formas.

La razón principal para no reutilizar Collector s, como se ve con los Collectors integrados, es que no se puede hacer de una manera segura. Cuando se ofrece un recopilador para listas escritas de manera arbitraria, necesitará operaciones sin marcar para entregar siempre la misma instancia de Collector . Si en su lugar almacena un Collector en una variable correctamente tipada, para ser utilizado sin operaciones no verificadas, puede usarlo solo para un tipo de List , para permanecer con ese ejemplo.

En el caso de Collections.emptyList() , etc., los desarrolladores de JRE EMPTY_LIST una forma diferente, pero las constantes EMPTY_LIST , EMPTY_MAP , EMPTY_SET ya existían antes de la introducción de Generics, y yo diría que son más versátiles que los pocos que se pueden almacenar en caché Collectors , que son solo cuatro casos especiales de los otros más de treinta coleccionistas incorporados, que no pueden almacenarse en caché debido a sus parámetros de función. Dado que los parámetros de funciones a menudo se implementan a través de expresiones lambda, que generan objetos de identidad / igualdad no especificada, una caché mapeándolas a instancias de coleccionista tendría una eficacia impredecible, pero muy probablemente menos eficiente de lo que el administrador de memoria se ocupará de las instancias temporales.


El clásico problema de usar un único objeto estático para sustituir a uno creado sobre la marcha es la mutabilidad. Un escaneo rápido de la fuente Java 8 resalta el campo Set<Characteristics> como un posible problema.

Claramente sería posible que algún código en alguna parte hiciera algo como:

private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList(); public void test() { // Any method could do this (no idea why but it should be possible). TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH); }

Esto podría cambiar globalmente la funcionalidad de cada uso de TO_LIST que podría crear errores muy oscuros.

Así que en mi humilde opinión, ¡no!


Es una buena práctica para una biblioteca proporcionar un método de fábrica para obtener objetos útiles. Como la biblioteca ha proporcionado este método: Collectors.toList() , también es una buena práctica dejar que la biblioteca decida si crear una nueva instancia cada vez que se solicita o no el objeto, en lugar de alterar la biblioteca, disminuyendo así legibilidad y arriesgando los problemas futuros cuando la implementación cambia.

Esto se agregará a la respuesta de GhostCat y Holger como argumento de apoyo :)


Este sería un caso de optimización prematura. La creación de objetos es bastante barata. En una computadora portátil normal, esperaría poder crear objetos entre 10M-50M por segundo. Con estos números en mente, todo el ejercicio pierde sentido.


Solo una pequeña nota al margen, lo que dice @Holger en su respuesta sobre el hecho de que el optimizador es inteligente y reemplaza completamente ese constructo es totalmente factible y se lo llama scalar replacement . Cuando un Objeto utilizado dentro de un método se deconstruye y sus campos se stack allocated like normal local variables . Para que el Collector resultante no se trate en el nivel JVM como un objeto per se. Eso pasaría en el JIT time .