Puedo agregar secuencias o elementos adicionales, como este:
Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));
Y puedo agregar cosas nuevas a medida que avanzo, así:
Stream stream = Stream.concat(
Stream.concat(
stream1.filter(x -> x!=0), stream2)
.filter(x -> x!=1),
Stream.of(element))
.filter(x -> x!=2);
Pero esto es feo, porque concat
es estático. Si concat
fuera un método de instancia, los ejemplos anteriores serían mucho más fáciles de leer:
Stream stream = stream1.concat(stream2).concat(element);
Y
Stream stream = stream1
.filter(x -> x!=0)
.concat(stream2)
.filter(x -> x!=1)
.concat(element)
.filter(x -> x!=2);
Mi pregunta es:
1) ¿Hay alguna buena razón por la cual concat
es estática? ¿O hay algún método de instancia equivalente que me falta?
2) En cualquier caso, ¿hay una mejor manera de hacerlo?
java
concat
java-8
java-stream
MarcG
fuente
fuente
Respuestas:
Si agrega importaciones estáticas para Stream.concat y Stream.of , el primer ejemplo podría escribirse de la siguiente manera:
La importación de métodos estáticos con nombres genéricos puede generar un código que se vuelve difícil de leer y mantener ( contaminación del espacio de nombres ). Por lo tanto, podría ser mejor crear sus propios métodos estáticos con nombres más significativos. Sin embargo, para la demostración me quedaré con este nombre.
Con estos dos métodos estáticos (opcionalmente en combinación con importaciones estáticas), los dos ejemplos podrían escribirse de la siguiente manera:
El código ahora es significativamente más corto. Sin embargo, estoy de acuerdo en que la legibilidad no ha mejorado. Entonces tengo otra solución.
En muchas situaciones, los recopiladores se pueden usar para ampliar la funcionalidad de las transmisiones. Con los dos recopiladores en la parte inferior, los dos ejemplos podrían escribirse de la siguiente manera:
La única diferencia entre la sintaxis deseada y la sintaxis anterior es que debe reemplazar concat (...) con collect (concat (...)) . Los dos métodos estáticos se pueden implementar de la siguiente manera (opcionalmente en combinación con importaciones estáticas):
Por supuesto, hay una desventaja con esta solución que debería mencionarse. recoger es una operación final que consume todos los elementos de la secuencia. Además de eso, el recopilador concat crea una ArrayList intermedia cada vez que se usa en la cadena. Ambas operaciones pueden tener un impacto significativo en el comportamiento de su programa. Sin embargo, si la legibilidad es más importante que el rendimiento , aún podría ser un enfoque muy útil.
fuente
concat
colector sea muy legible. Parece extraño tener un método estático de un solo parámetro llamado así, y también usarlocollect
para la concatenación.It's a bad idea to import static methods with names
? Estoy realmente interesado: creo que hace que el código sea más conciso y legible, y muchas personas a las que les pregunté pensaron lo mismo. ¿Quieres dar algunos ejemplos de por qué eso es generalmente malo?compare(reverse(getType(42)), of(6 * 9).hashCode())
? Tenga en cuenta que no dije que las importaciones estáticas son una mala idea, pero las importaciones estáticas para nombres genéricos comoof
yconcat
son.Lamentablemente, esta respuesta probablemente sea de poca o ninguna ayuda, pero hice un análisis forense de la lista de correo Java Lambda para ver si podía encontrar la causa de este diseño. Esto es lo que descubrí.
Al principio había un método de instancia para Stream.concat (Stream)
En la lista de correo puedo ver claramente que el método se implementó originalmente como un método de instancia, como puede leer en este hilo de Paul Sandoz, sobre la operación concat.
En él discuten los problemas que podrían surgir de aquellos casos en los que la secuencia podría ser infinita y qué significaría la concatenación en esos casos, pero no creo que esa sea la razón de la modificación.
Ves en este otro hilo que algunos de los primeros usuarios del JDK 8 cuestionaron el comportamiento del método de instancia concat cuando se usaban con argumentos nulos.
Sin embargo, este otro hilo revela que el diseño del método concat estaba en discusión.
Refactorizado a Streams.concat (Stream, Stream)
Pero sin ninguna explicación, de repente, los métodos se cambiaron a métodos estáticos, como puede ver en este hilo sobre la combinación de secuencias . Este es quizás el único hilo de correo que arroja un poco de luz sobre este cambio, pero no fue lo suficientemente claro para determinar el motivo de la refactorización. Pero podemos ver que hicieron un commit en el que sugirieron mover el
concat
método fuera deStream
la clase auxiliarStreams
.Refactorizado a Stream.concat (Stream, Stream)
Más tarde, se movió nuevamente de
Streams
aStream
, pero una vez más, no hay explicación para eso.Entonces, en resumen, la razón del diseño no está del todo clara para mí y no pude encontrar una buena explicación. Supongo que aún podrías hacer la pregunta en la lista de correo.
Algunas alternativas para la concatenación de corrientes
Este otro hilo de Michael Hixson discute / pregunta sobre otras formas de combinar / concat streams
fuente
public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
@SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
Function.identity()
mapa? Después de todo, devuelve el mismo argumento que recibe. Esto no debería tener ningún efecto en la secuencia resultante. ¿Me estoy perdiendo de algo?return Stream.of(streams).reduce(Stream.empty(),Stream::concat)
devuelve Stream <? extiende T>. (Algo <T> es subtipo de Algo <? extiende T>, no al revés, por lo que no se puede lanzar) ¿Lanzamiento adicional.map(identity())
<? extiende T> a <T>. Ocurre gracias a la combinación de java 8 'tipos de destino' de argumentos de métodos y tipos de retorno y firma del método map (). En realidad es Function. <T> identity ().? extends T
, ya que puedes usar la conversión de captura . En cualquier caso, aquí está mi fragmento de código de Gist. Continuemos la discusión en Gist.Mi biblioteca StreamEx amplía la funcionalidad de Stream API. En particular, ofrece métodos como agregar y anteponer que resuelven este problema (internamente lo usan
concat
). Estos métodos pueden aceptar otra secuencia o colección o matriz de varargs. Usando mi biblioteca, su problema se puede resolver de esta manera (tenga en cuenta quex != 0
parece extraño para la secuencia no primitiva):Por cierto, también hay un atajo para su
filter
operación:fuente
Solo haz:
donde
identity()
es una importación estática deFunction.identity()
.Concatenar múltiples flujos en un flujo es lo mismo que aplanar un flujo.
Sin embargo, desafortunadamente, por alguna razón no hay ningún
flatten()
método activadoStream
, por lo que debe usarloflatMap()
con la función de identidad.fuente
Puede usar el método de Guava , que será muy corto con las importaciones estáticas:
Streams
.
concat(Stream<? extends T>... streams)
fuente
Si no le importa usar Bibliotecas de terceros, cyclops-react tiene un tipo de Stream extendido que le permitirá hacer eso a través de los operadores anexar / anteponer.
Los valores individuales, las matrices, los iterables, las secuencias o las secuencias reactivas Los editores se pueden agregar y agregar como métodos de instancia.
[Divulgación Soy el desarrollador principal de cyclops-react]
fuente
Al final del día, no estoy interesado en combinar secuencias, sino en obtener el resultado combinado de procesar cada elemento de todas esas secuencias.
Si bien la combinación de secuencias puede resultar engorrosa (por lo tanto, este hilo), combinar sus resultados de procesamiento es bastante fácil.
La clave para resolver es crear su propio recopilador y asegurarse de que la función de proveedor para el nuevo recopilador devuelva la misma colección cada vez ( no una nueva ), el siguiente código ilustra este enfoque.
fuente
¿Qué tal escribir tu propio método concat?
Esto al menos hace que tu primer ejemplo sea mucho más legible.
fuente