procesamiento - Cómo agregar elementos de una secuencia Java8 en una lista existente
procesamiento de datos con streams de java se 8-parte 2 (5)
Hasta donde puedo ver, todas las otras respuestas hasta ahora usaban un recopilador para agregar elementos a una transmisión existente. Sin embargo, hay una solución más corta, y funciona tanto para secuencias secuenciales como paralelas. Simplemente puede usar el método forEachOrdered en combinación con una referencia de método.
List<String> source = ...;
List<Integer> target = ...;
source.stream()
.map(String::length)
.forEachOrdered(target::add);
La única restricción es que la fuente y el destino son listas diferentes, ya que no está permitido realizar cambios en el origen de una transmisión, siempre que se procese.
Tenga en cuenta que esta solución funciona tanto para secuencias secuenciales como paralelas. Sin embargo, no se beneficia de concurrencia. La referencia de método pasada a forEachOrdered siempre se ejecutará secuencialmente.
Javadoc of Collector muestra cómo recopilar elementos de una secuencia en una nueva lista. ¿Existe un diseño único que agregue los resultados a una ArrayList existente?
Solo tiene que referir su lista original para que sea la que devuelva Collectors.toList()
.
Aquí hay una demostración:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Reference {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list);
// Just collect even numbers and start referring the new list as the original one.
list = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(list);
}
}
Y así es como puede agregar los elementos recién creados a su lista original en una sola línea.
List<Integer> list = ...;
// add even numbers from the list to the list again.
list.addAll(list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList())
);
Eso es lo que ofrece este Paradigma de Programación Funcional.
La respuesta corta es no (o debería ser no). EDITAR: sí, es posible (ver la respuesta de assylias abajo), pero sigue leyendo. EDIT2: ¡ pero vea la respuesta de Stuart Marks por otra razón más por la que todavía no debería hacerlo!
La respuesta más larga:
El propósito de estos constructos en Java 8 es introducir algunos conceptos de Programación Funcional al lenguaje; en la Programación funcional, las estructuras de datos no suelen modificarse; en cambio, las nuevas se crean a partir de las antiguas mediante transformaciones como mapa, filtro, plegado / reducción y muchas otras.
Si debe modificar la lista anterior, simplemente recopile los elementos asignados en una nueva lista:
final List<Integer> newList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
y luego list.addAll(newList)
- nuevamente: si realmente debes hacerlo.
(o construya una lista nueva que concatene la antigua y la nueva, y asígnela a la variable de list
; esto es un poco más en el espíritu de FP que addAll
)
En cuanto a la API: a pesar de que la API lo permite (de nuevo, ver la respuesta de assylias), debes intentar evitarlo independientemente, al menos en general. Lo mejor es no luchar contra el paradigma (FP) y tratar de aprenderlo en lugar de luchar contra él (aunque Java en general no es un lenguaje FP), y solo recurrir a tácticas "más sucias" si es absolutamente necesario.
La respuesta realmente larga: (es decir, si incluye el esfuerzo de encontrar y leer realmente una introducción / libro FP como se sugiere)
Para saber por qué modificar las listas existentes es en general una mala idea y conduce a un código menos sostenible, a menos que esté modificando una variable local y su algoritmo es corto y / o trivial, lo cual está fuera del alcance de la cuestión del mantenimiento del código -busque una buena introducción a la Programación Funcional (hay cientos) y comience a leer. Una explicación de "vista previa" sería algo así como: es más matemáticamente sano y más fácil razonar para no modificar los datos (en la mayoría de las partes de su programa) y conduce a un nivel más alto y menos técnico (y más amigable para el ser humano). transiciones alejadas del pensamiento imperativo de estilo antiguo) definiciones de lógica de programa.
NOTA: la respuesta de nosid muestra cómo agregar a una colección existente usando forEachOrdered()
. Esta es una técnica útil y efectiva para mutar colecciones existentes. Mi respuesta aborda por qué no debe usar un Collector
para mutar una colección existente.
La respuesta corta es no , al menos, no en general, no debe usar un Collector
para modificar una colección existente.
La razón es que los colectores están diseñados para admitir el paralelismo, incluso en colecciones que no son seguras para subprocesos. La forma en que lo hacen es hacer que cada hilo funcione independientemente en su propia colección de resultados intermedios. La forma en que cada thread obtiene su propia colección es llamar al Collector.supplier()
que se requiere para devolver una nueva colección cada vez.
Estas colecciones de resultados intermedios luego se fusionan, nuevamente de manera confinada, hasta que haya una sola colección de resultados. Este es el resultado final de la operación de collect()
.
Un par de respuestas de Balder y assylias sugirieron usar Collectors.toCollection()
y luego pasar a un proveedor que devuelve una lista existente en lugar de una nueva lista. Esto infringe el requisito del proveedor, que es que devuelva una nueva colección vacía cada vez.
Esto funcionará para casos simples, como lo demuestran los ejemplos en sus respuestas. Sin embargo, fallará, particularmente si la transmisión se ejecuta en paralelo. (Una versión futura de la biblioteca puede cambiar de alguna manera imprevista que hará que falle, incluso en el caso secuencial).
Tomemos un ejemplo simple:
List<String> destList = new ArrayList<>(Arrays.asList("foo"));
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
newList.parallelStream()
.collect(Collectors.toCollection(() -> destList));
System.out.println(destList);
Cuando ejecuto este programa, a menudo obtengo una ArrayIndexOutOfBoundsException
. Esto se debe a que varios subprocesos están operando en ArrayList
, una estructura de datos insegura de subprocesos. OK, hagámoslo sincronizado:
List<String> destList =
Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));
Esto ya no fallará con una excepción. Pero en lugar del resultado esperado:
[foo, 0, 1, 2, 3]
da resultados extraños como este:
[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]
Este es el resultado de las operaciones de acumulación / fusión limitadas por hilos que describí anteriormente. Con un flujo paralelo, cada hilo llama al proveedor para obtener su propia colección de acumulación intermedia. Si pasa a un proveedor que devuelve la misma colección, cada hilo agrega sus resultados a esa colección. Como no hay orden entre los hilos, los resultados se anexarán en un orden arbitrario.
Entonces, cuando estas colecciones intermedias se fusionan, esto básicamente combina la lista consigo mismo. Las listas se combinan utilizando List.addAll()
, que dice que los resultados no están definidos si la colección de origen se modifica durante la operación. En este caso, ArrayList.addAll()
realiza una operación de copia de matriz, por lo que termina duplicándose a sí mismo, lo que es una especie de lo que cabría esperar, supongo. (Tenga en cuenta que otras implementaciones de List pueden tener un comportamiento completamente diferente.) De todos modos, esto explica los resultados extraños y los elementos duplicados en el destino.
Podrías decir: "Solo me aseguraré de ejecutar mi secuencia secuencialmente" y seguir adelante y escribir código como este
stream.collect(Collectors.toCollection(() -> existingList))
de todas formas. Yo recomendaría no hacer esto. Si controlas la transmisión, puedes garantizar que no se ejecutará en paralelo. Espero que surja un estilo de programación donde las transmisiones se distribuyen en lugar de las colecciones. Si alguien te entrega una transmisión y usas este código, fallará si la transmisión es paralela. Peor aún, alguien podría darle un flujo secuencial y este código funcionará bien por un tiempo, pasará todas las pruebas, etc. Luego, una cantidad arbitraria de tiempo más tarde, el código en cualquier parte del sistema podría cambiar para usar transmisiones paralelas que causarán su código romper.
De acuerdo, solo asegúrese de recordar llamar a sequential()
en cualquier secuencia antes de usar este código:
stream.sequential().collect(Collectors.toCollection(() -> existingList))
Por supuesto, recordarás hacer esto cada vez, ¿verdad? :-) Digamos que sí. Entonces, el equipo de rendimiento se preguntará por qué todas sus implementaciones paralelas cuidadosamente diseñadas no proporcionan ninguna aceleración. Y una vez más, lo rastrearán hacia su código, lo que obliga a que toda la secuencia se ejecute secuencialmente.
No lo hagas
Erik Allik ya dio muy buenas razones, por qué lo más probable es que no quiera recopilar elementos de una transmisión en una Lista existente.
De todos modos, puede usar el siguiente delineador, si realmente necesita esta funcionalidad.
EDITAR: Pero como explica Stuart Marks en su respuesta, nunca debes hacer esto, si las transmisiones pueden ser transmisiones paralelas, úsalas bajo tu propio riesgo ...
list.stream().collect(Collectors.toCollection(() -> myExistingList));