Recuperando una lista de un java.util.stream.Stream en Java 8


443

Estaba jugando con Java 8 lambdas para filtrar fácilmente las colecciones. Pero no encontré una forma concisa de recuperar el resultado como una nueva lista dentro de la misma declaración. Aquí está mi enfoque más conciso hasta ahora:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Los ejemplos en la red no respondieron a mi pregunta porque se detienen sin generar una nueva lista de resultados. Debe haber una manera más concisa. Lo que habría esperado, que la Streamclase tiene métodos como toList(), toSet()...

¿Hay alguna manera de que las variables targetLongListpuedan ser asignadas directamente por la tercera línea?


77
En caso de que no necesite el siguiente, sourceLongListes Collection.removeIf(…)conveniente.
Holger

2
¿Qué tal esto? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah

Respuestas:


609

Lo que está haciendo puede ser la forma más sencilla, siempre que su transmisión permanezca secuencial; de lo contrario, tendrá que poner una llamada a secuencial () antes forEach.

[Edición posterior: la razón por la cual la llamada a secuencial () es necesaria es que el código tal como está ( forEach(targetLongList::add)) sería picante si la secuencia fuera paralela. Incluso entonces, no logrará el efecto deseado, ya que forEaches explícitamente no determinista, incluso en una secuencia secuencial no se garantiza el orden del procesamiento del elemento. Tendría que usar forEachOrderedpara garantizar un pedido correcto. La intención de los diseñadores de Stream API es que utilizará el recopilador en esta situación, como se muestra a continuación.]

Una alternativa es

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());

10
Además: creo que este código se vuelve un poco más corto, más claro y más bonito si usa una importación estática de toList. Esto se realiza mediante la colocación de la siguiente entre las importaciones del archivo: static import java.util.stream.Collectors.toList;. Entonces la llamada por cobrar se lee solo .collect(toList()).
Lii

44
En Eclipse es posible hacer que el IDE agregue una importación estática para los métodos. Esto se hace agregando la Collectorsclase en Preferencias -> Java -> Editor -> Content Assist -> Favoritos . Después de esto, solo tiene que escribir toLial presionar Ctr + Espacio para que el IDE se complete toListy agregue la importación estática.
Lii

3
Una cosa a tener en cuenta es que IntStreamy algunos otros casi pero no del Streamtodo no tienen el collect(Collector)método y primero tendrá que llamar IntStream.boxed()para convertirlos en normales Stream. Por otra parte, tal vez solo quieras toArray().
Mutant Bob

por qué tenemos que usar sequential()antes forEacho usar 'forEachOrdered`
amarnath harish

1
@amarnathharish Porque forEach no garantiza el orden de ejecución de la operación para una secuencia paralela. JavaDoc dice "El comportamiento de esta operación es explícitamente no determinista. Para las tuberías de flujo paralelo, esta operación no garantiza respetar el orden de encuentro del flujo, ya que hacerlo sacrificaría el beneficio del paralelismo". (La primera oración de esta cita en realidad significa que el orden tampoco está garantizado para flujos secuenciales, aunque en la práctica se conserva).
Maurice Naftalin,

183

Actualizado:

Otro enfoque es usar Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Solución anterior:

Otro enfoque es usar Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));

6060
Sin embargo, esto es útil si desea una implementación particular de la Lista.
orbfish

1
A pesar de que se recomienda codificar contra interfaces, hay casos claros (uno de ellos es GWT) cuando tiene que codificar contra implementaciones concretas (a menos que desee que todas las implementaciones de Lista se compilen y entreguen como javascript).
Eduard Korenschi

3
Otro profesional para este método, del Collectors::toListjavadoc: "No hay garantías sobre el tipo, mutabilidad, serialización o seguridad de subprocesos de la Lista devuelta; si se requiere más control sobre la Lista devuelta, úsela toCollection(Supplier)".
Charles Wood

12

Me gusta usar un método util que devuelve un recopilador para ArrayListcuando eso es lo que quiero.

Creo que usar la solución Collectors.toCollection(ArrayList::new)es demasiado ruidoso para una operación tan común.

Ejemplo:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Con esta respuesta, también quiero demostrar cuán simple es crear y usar recopiladores personalizados, lo cual es muy útil en general.


Si declara el resultado como List <Long>, no necesita usar este método util. Collectors.toList lo hará. Además, el uso de clases específicas en lugar de interfaces es un olor a código.
Lluis Martinez

1
@LluisMartinez: "Collectors.toList servirá". : No, no en muchas situaciones. Porque no es una buena idea usar toListsi, por ejemplo, desea modificar la lista más adelante en el programa. La toListdocumentación dice esto: "No hay garantías sobre el tipo, mutabilidad, serialización o seguridad de subprocesos de la Lista devuelta; si se requiere más control sobre la Lista devuelta, úsela toCollection". . Mi respuesta demuestra una forma de hacer que sea más conveniente hacerlo en un caso común.
Lii

Si desea crear específicamente una ArrayList, entonces está bien.
Lluis Martinez

5

Si tiene una variedad de primitivas, puede usar las colecciones primitivas disponibles en Eclipse Collections .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Si no puede cambiar la fuenteLongList de List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Si quieres usar LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Nota: Soy colaborador de Eclipse Collections.


4

Una forma un poco más eficiente (evite la creación de la Lista fuente y el desempaquetado automático por el filtro):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());


1

Si no le importa usar bibliotecas de terceros, la biblioteca cyclops-react lib de AOL (revelación de que soy colaborador) tiene extensiones para todos los tipos de Colección JDK , incluida la Lista . La interfaz ListX extiende java.util.List y agrega una gran cantidad de operadores útiles, incluido el filtro.

Puedes simplemente escribir

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX también se puede crear a partir de una lista existente (a través de ListX.fromIterable)


1

Existe otra variante del método de recopilación proporcionada por la clase LongStream y, de manera similar, también por las clases IntStream y DoubleStream.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Realiza una operación de reducción mutable en los elementos de esta secuencia. Una reducción mutable es aquella en la que el valor reducido es un contenedor de resultados mutables, como un ArrayList, y los elementos se incorporan actualizando el estado del resultado en lugar de reemplazarlo. Esto produce un resultado equivalente a:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Al igual que reduce (long, LongBinaryOperator), las operaciones de recopilación pueden paralelizarse sin requerir sincronización adicional. Esta es una operación terminal.

Y la respuesta a su pregunta con este método de recopilación es la siguiente:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

A continuación se muestra la variante de referencia del método, que es bastante inteligente pero algo difícil de entender:

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

A continuación se mostrará la variante HashSet:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

De manera similar, la variante LinkedList es así:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);

0

En caso de que alguien (como yo) esté buscando formas de tratar con Objetos en lugar de tipos primitivos, use mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

huellas dactilares:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o

0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));

0

Aquí está el código de AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Divulgación: Soy el desarrollador de AbacusUtil.


No encuentro ningún método toList presente en la clase LongStream. ¿Podrías ejecutar este código?
Vaneet Kataria

@VaneetKataria prueba com.landawn.abacus.util.stream.LongStreamo LongStreamExen AbacusUtil
user_3380739

0

Puede reescribir el código de la siguiente manera:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());

Gracias por tu contribución. Sin embargo, explique qué cambió y en qué medida se relaciona con la pregunta.
B - rian

Aquí, en primer lugar, convertí mi ArrayList para vaporizarlos usando el filtro. Filtre los datos requeridos. Finalmente, he usado el método de recopilación de Java 8 Stream para recopilar datos en una nueva lista llamada targetLongList.
Pratik Pawar

0

Para recopilar en una lista mutable:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

Para recopilar en una lista inmutable:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Explicación de collectdesde el JavaDoc :

Realiza una operación de reducción mutable en los elementos de esta secuencia utilizando un recopilador. Un recopilador encapsula las funciones utilizadas como argumentos para recopilar (Proveedor, BiConsumer, BiConsumer), lo que permite la reutilización de estrategias de recopilación y la composición de las operaciones de recopilación, como la agrupación o partición de múltiples niveles. Si la secuencia es paralela y el recopilador es concurrente, y la secuencia no está ordenada o el recopilador no está ordenado, se realizará una reducción concurrente (consulte el recopilador para obtener detalles sobre la reducción concurrente).

Esta es una operación terminal.

Cuando se ejecuta en paralelo, se pueden instanciar, completar y combinar múltiples resultados intermedios para mantener el aislamiento de las estructuras de datos mutables. Por lo tanto, incluso cuando se ejecuta en paralelo con estructuras de datos no seguras para subprocesos (como ArrayList), no se necesita sincronización adicional para una reducción paralela.


-3

Si no usas parallel()esto funcionará

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());

No me gusta que el método collect () solo se use para conducir la transmisión, de modo que se llame al gancho peek () en cada elemento. El resultado de la operación del terminal se descarta.
Daniel K.

Es muy extraño llamar collecty luego no guardar el valor de retorno. En ese caso, podría usar forEachen su lugar. Pero esa sigue siendo una mala solución.
Lii

1
Usar peek () de esta manera es un antipatrón.
Grzegorz Piwowarek

Según el método Stream Java docs peek se debe usar solo con fines de depuración. No se debe usar para ningún procesamiento que no sea la depuración.
Vaneet Kataria
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.