Recupero di un elenco da java.util.stream.Stream in Java 8


443

Stavo giocando con lambda Java 8 per filtrare facilmente le raccolte. Ma non ho trovato un modo conciso per recuperare il risultato come un nuovo elenco all'interno della stessa istruzione. Ecco il mio approccio più conciso finora:

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);

Gli esempi in rete non hanno risposto alla mia domanda perché si fermano senza generare un nuovo elenco di risultati. Deve esserci un modo più conciso. Mi sarei aspettato, che la Streamclasse ha metodi come toList(), toSet(), ...

C'è un modo in cui le variabili targetLongListpossono essere assegnate direttamente dalla terza riga?


7
Nel caso in cui non ti serva il sourceLongListseguito, Collection.removeIf(…)per comodità.
Holger

2
Cosa ne pensi di questo? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah,

Risposte:


609

Quello che stai facendo potrebbe essere il modo più semplice, a condizione che il tuo stream rimanga sequenziale, altrimenti dovrai prima chiamare una chiamata sequenziale () forEach.

[modifica successiva: la ragione per cui la chiamata a sequential () è necessaria è che il codice così com'è ( forEach(targetLongList::add)) sarebbe filante se il flusso fosse parallelo. Anche in questo caso, non otterrà l'effetto previsto, come forEachè esplicitamente non deterministico, anche in un flusso sequenziale l'ordine di elaborazione degli elementi non è garantito. Dovresti utilizzare forEachOrderedper garantire un ordine corretto. L'intenzione dei progettisti dell'API Stream è quella di utilizzare il raccoglitore in questa situazione, come di seguito.]

Un'alternativa è

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

10
Aggiunta: penso che questo codice diventi un po 'più breve, più chiaro e più bello se usi un'importazione statica di toList. Questo viene fatto inserendo il seguente tra le importazioni del file: static import java.util.stream.Collectors.toList;. Quindi la chiamata di raccolta legge solo .collect(toList()).
Lii,

4
In Eclipse è possibile fare in modo che l'IDE aggiunga un'importazione statica per i metodi. Questo viene fatto aggiungendo la Collectorsclasse in Preferenze -> Java -> Editor -> Content Assist -> Preferiti . Dopo questo, devi solo digitare toLia colpo Ctr + Spazio per avere il riempimento IDE toListe aggiungere l'importazione statica.
Lii,

3
Una cosa da tenere a mente è che IntStreame alcuni altri quasi ma non abbastanza Streamnon hanno il collect(Collector)metodo e dovrai chiamare IntStream.boxed()per convertirli in un normale Streamprima. Poi di nuovo, forse vuoi solo toArray().
Mutante Bob,

perché dobbiamo usare sequential()prima forEacho usare 'forEachOrdered'
amarnath harish

1
@amarnathharish Perché forEach non garantisce l'ordine di esecuzione dell'operazione per un flusso parallelo. JavaDoc afferma "Il comportamento di questa operazione è esplicitamente non deterministico. Per le pipeline di flusso parallele, questa operazione non garantisce il rispetto dell'ordine di incontro del flusso, poiché ciò sacrificerebbe il vantaggio del parallelismo". (La prima frase di questa citazione in realtà significa che l'ordine non è garantito neanche per i flussi sequenziali, sebbene in pratica sia preservato.)
Maurice Naftalin,

183

aggiornato:

Un altro approccio è usare Collectors.toList:

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

Soluzione precedente:

Un altro approccio è usare Collectors.toCollection:

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

60
Ciò è, tuttavia, utile se si desidera un'implementazione dell'elenco specifica.
orbfish

1
Nonostante sia stato raccomandato di codificare in base alle interfacce, ci sono casi chiari (uno dei quali è GWT) quando è necessario codificare in base a implementazioni concrete (a meno che non si desideri compilare e consegnare tutte le implementazioni di List come javascript).
Eduard Korenschi,

3
Un altro pro per questo metodo, dal Collectors::toListjavadoc: "Non ci sono garanzie sul tipo, la mutabilità, la serializzabilità o la sicurezza dei thread dell'elenco restituito; se è necessario un maggiore controllo sull'elenco restituito, utilizzare toCollection(Supplier)".
Charles Wood,

12

Mi piace usare un metodo util che restituisce un collezionista per ArrayListquando è quello che voglio.

Penso che la soluzione utilizzata Collectors.toCollection(ArrayList::new)sia un po 'troppo rumorosa per un'operazione così comune.

Esempio:

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 questa risposta voglio anche dimostrare quanto sia semplice creare e utilizzare collezionisti personalizzati, il che è molto utile in generale.


Se si dichiara risultato come Elenco <Lungo> non è necessario utilizzare questo metodo util. Lo farà Collectors.toList. Inoltre, l'utilizzo di classi specifiche anziché di interfacce è un odore di codice.
Lluis Martinez,

1
@LluisMartinez: "Collectors.toList farà." : No, non in molte situazioni. Perché non è una buona idea utilizzare toListse, ad esempio, si desidera modificare l'elenco in un secondo momento nel programma. La toListdocumentazione dice questo: "Non ci sono garanzie sul tipo, la mutabilità, la serializzabilità o la sicurezza dei thread dell'elenco restituito; se è necessario un maggiore controllo sull'elenco restituito, utilizzare toCollection". . La mia risposta dimostra un modo per renderlo più conveniente farlo in un caso comune.
Lii,

Se vuoi creare una ArrayList specifica, allora va bene.
Lluis Martinez,

5

Se si dispone di una matrice di primitive, è possibile utilizzare le raccolte di primitive disponibili nelle raccolte di Eclipse .

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

Se non puoi modificare sourceLongList da 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<>());

Se vuoi usare 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: sono un collaboratore delle raccolte Eclipse.


4

Un modo un po 'più efficiente (evita di creare l'Elenco sorgenti e l'auto-unboxing dal filtro):

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


1

Se non ti dispiace usare librerie di terze parti, la libreria AOL con reazione al ciclope (la divulgazione che io sono un collaboratore) ha estensioni per tutti i tipi di raccolte JDK , incluso List . L'interfaccia ListX estende java.util.List e aggiunge un gran numero di operatori utili, incluso il filtro.

Puoi semplicemente scrivere-

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

ListX può anche essere creato da un Elenco esistente (tramite ListX.fromIterable)


1

Esiste un'altra variante del metodo di raccolta fornito dalla classe LongStream e in modo simile anche dalle classi IntStream e DoubleStream.

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

Esegue un'operazione di riduzione mutabile sugli elementi di questo flusso. Una riduzione mutabile è quella in cui il valore ridotto è un contenitore di risultati mutabile, come un ArrayList, e gli elementi vengono incorporati aggiornando lo stato del risultato anziché sostituendolo. Questo produce un risultato equivalente a:

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

Come ridurre (long, LongBinaryOperator), le operazioni di raccolta possono essere parallelizzate senza richiedere ulteriore sincronizzazione. Questa è un'operazione terminale.

E la risposta alla tua domanda con questo metodo di raccolta è la seguente:

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

Di seguito è riportata la variante di riferimento del metodo che è abbastanza intelligente ma alcuni che sono difficili da capire:

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

Di seguito sarà la variante di HashSet:

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

Allo stesso modo la variante di LinkedList è così:

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

0

Nel caso in cui qualcuno (come me) là fuori stia cercando dei modi per gestire gli oggetti anziché i tipi primitivi, quindi usarli 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);

stampe:

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

Ecco il codice di AbacusUtil

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

Divulgazione : Sono lo sviluppatore di AbacusUtil.


Non trovo alcun metodo toList presente nella classe LongStream. Potresti eseguire questo codice?
Vaneet Kataria,

@VaneetKataria try com.landawn.abacus.util.stream.LongStreamor LongStreamExin AbacusUtil
user_3380739

0

Puoi riscrivere il codice come di seguito:

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());

Grazie per il tuo contributo. Tuttavia, ti preghiamo di spiegare cosa hai cambiato e in che misura si riferisce alla domanda.
B - rian,

Qui in primo luogo ho convertito la mia ArrayList in steam usando il filtro I filtrare i dati richiesti. Finalmente ho usato il metodo di raccolta del flusso java 8 per raccogliere dati nel nuovo elenco chiamato targetLongList.
Pratik Pawar,

0

Per raccogliere in un elenco mutabile:

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

Per raccogliere in un elenco immutabile:

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

Spiegazione di collectda JavaDoc :

Esegue un'operazione di riduzione mutabile sugli elementi di questo flusso utilizzando un servizio di raccolta. Un Collector incapsula le funzioni utilizzate come argomenti da raccogliere (Fornitore, BiConsumer, BiConsumer), consentendo il riutilizzo delle strategie di raccolta e la composizione delle operazioni di raccolta come il raggruppamento o il partizionamento a più livelli. Se il flusso è parallelo e Collector è simultaneo e il flusso non è ordinato o il collector non è ordinato, verrà eseguita una riduzione simultanea (vedere Collector per dettagli sulla riduzione simultanea).

Questa è un'operazione terminale.

Se eseguiti in parallelo, è possibile creare istanze, compilare e unire più risultati intermedi in modo da mantenere l'isolamento delle strutture di dati mutabili. Pertanto, anche se eseguito in parallelo con strutture dati non thread-safe (come ArrayList), non è necessaria alcuna sincronizzazione aggiuntiva per una riduzione parallela.


-3

Se non lo usi parallel(), funzionerà

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());

Non mi piace il fatto che collect () sia usato solo per guidare il flusso in modo che il gancio peek () sia chiamato su ogni elemento. Il risultato dell'operazione del terminale viene scartato.
Daniel K.

È molto strano chiamare collecte quindi non salvare il valore restituito. In tal caso forEachinvece potresti usare . Ma questa è ancora una soluzione scadente.
Lii,

1
L'uso di peek () in questo modo è un antipattern.
Grzegorz Piwowarek,

Secondo il flusso di documenti Java, il metodo peek deve essere utilizzato solo a scopo di debug. Non deve essere utilizzato per elaborazioni diverse dal debug.
Vaneet Kataria,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.