2015-06-10 17 views
7

consideri il seguente esempio che stampa il massimo elemento di un List:scelta tra Stream e collezioni API

List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);   
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println); 

Lo stesso obiettivo può essere raggiunto utilizzando il metodo Collections.max:

System.out.println(Collections.max(list)); 

che questo il codice non è solo più breve ma anche più pulito da leggere (secondo me). Ci sono esempi simili che vengono in mente, come l'uso di binarySearch rispetto allo filter utilizzato insieme a findAny.

Capisco che Stream può essere una pipeline infinita rispetto a una Collection che è limitata dalla memoria disponibile per la JVM. Questo sarebbe il mio criterio per decidere se utilizzare l'API Stream o Collections. Ci sono altri motivi per cui scegliere Stream sull'API Collections (ad esempio le prestazioni). Più in generale, è questa l'unica ragione per scegliere Stream su API meno recenti in grado di svolgere il lavoro in modo più pulito e più breve?

+0

Stai chiedendo solo su questo metodo specifico? –

+0

Se il tuo unico interesse è ottenere il valore 'max' dalla lista, il metodo' Collections' è la strada da percorrere. – GriffeyDog

+0

Inoltre, il comportamento 'Opzionale' rispetto a' NoSuchElementException'. –

risposta

5

Stream API è come un coltellino svizzero: consente di eseguire operazioni complesse combinando gli strumenti in modo efficace. D'altra parte se hai solo bisogno di un cacciavite, probabilmente il cacciavite autonomo sarebbe più conveniente. Stream API include molte cose (come distinct, sorted, operazioni primitive, ecc.) Che altrimenti richiederebbero di scrivere diverse righe e introdurre variabili intermedie/strutture dati e cicli noiosi che attirano l'attenzione del programmatore dall'algoritmo corrente. A volte l'uso dell'API Stream può migliorare le prestazioni anche per il codice sequenziale. Ad esempio, considera alcune vecchie API:

class Group { 
    private Map<String, User> users; 

    public List<User> getUsers() { 
     return new ArrayList<>(users.values()); 
    } 
} 

Qui vogliamo restituire tutti gli utenti del gruppo. Il progettista dell'API ha deciso di restituire uno List. Ma può essere utilizzato al di fuori in vari modi:

List<User> users = group.getUsers(); 
Collections.sort(users); 
someOtherMethod(users.toArray(new User[users.size])); 

Qui è ordinato e convertito in array per passare a un altro metodo che è accaduto ad accettare un array. In altro luogo getUsers() può essere utilizzato in questo modo:

List<User> users = group.getUsers(); 
for(User user : users) { 
    if(user.getAge() < 18) { 
     throw new IllegalStateException("Underage user in selected group!"); 
    } 
} 

Qui vogliamo solo trovare l'utente corrisponde alcuni criteri. In entrambi i casi la copia su intermedio ArrayList non era effettivamente necessaria. Quando ci muoviamo a Java 8, possiamo sostituire getUsers() metodo con users():

public Stream<User> users() { 
    return users.values().stream(); 
} 

e modificare il codice chiamante. Il primo:

someOtherMethod(group.users().sorted().toArray(User[]::new)); 

Il secondo:

if(group.users().anyMatch(user -> user.getAge() < 18)) { 
    throw new IllegalStateException("Underage user in selected group!"); 
} 

In questo modo non è solo più breve, ma può lavorare più velocemente pure, perché saltiamo la copia intermedia.

L'altro punto concettuale in Stream API è che qualsiasi codice stream scritto in base alle linee guida può essere parallelizzato semplicemente aggiungendo il passo parallel(). Ovviamente questo non migliorerà sempre le prestazioni, ma aiuta più spesso di quanto mi aspettassi.Di solito se l'operazione eseguita sequenzialmente per 0.1ms or longer, può beneficiare della parallelizzazione. Ad ogni modo, non abbiamo mai visto un modo così semplice di fare la programmazione parallela in Java.

2

Naturalmente, dipende sempre dalle circostanze. Prendi il tuo primo esempio:

List<Integer> list = Arrays.asList(1,4,3,9,7,4,8);   
list.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println); 

Se si vuole fare la cosa stesso in modo efficiente, è necessario utilizzare

IntStream.of(1,4,3,9,7,4,8).max().ifPresent(System.out::println); 

che non comporta alcuna auto-boxing. Ma se la tua ipotesi è di avere un List<Integer> in anticipo, quella potrebbe non essere un'opzione, quindi se sei solo interessato al valore max, Collections.max potrebbe essere la scelta più semplice.

Ma questo porterebbe alla domanda perché si dispone di un List<Integer> in anticipo. Forse, è il risultato del vecchio codice (o del nuovo codice scritto usando il vecchio modo di pensare), che non ha avuto altra scelta che usare il pugilato e Collection s come non c'era alternativa nel passato?

Quindi forse dovresti pensare alla fonte che produce la collezione, prima di preoccuparti di come consumarla (o bene, pensa ad entrambi allo stesso tempo).

Se hai a disposizione un Collection e tutto ciò che serve è una singola operazione terminale per il quale esiste una semplice implementazione Collection base, si può utilizzare direttamente senza preoccuparsi con il Stream API. I progettisti dell'API hanno riconosciuto questa idea poiché hanno aggiunto metodi come forEach(…) all'API Collection invece di insistere su chiunque utilizzi stream().forEach(…). E Collection.forEach(…) non è una semplice scorciatoia per Collection.stream().forEach(…), infatti, è già definita sull'interfaccia più astratta Iterable che non ha nemmeno un metodo stream().

Btw., È necessario comprendere la differenza tra Collections.binarySearch e Stream.filter/findAny. Il primo richiede che la raccolta sia ordinata e se tale requisito è soddisfatto, potrebbe essere la scelta migliore. Ma se la raccolta non è ordinata, una semplice ricerca lineare è più efficiente dell'ordinamento solo per un singolo uso della ricerca binaria, per non parlare del fatto, che la ricerca binaria funziona con List s solo mentre filter/findAny funziona con qualsiasi flusso supportare ogni tipo di raccolta di fonti.

+0

Stai proponendo che qualsiasi nuovo codice che scrivo d'ora in poi non debba usare un 'List' o un' Collection' ma usi sempre un 'Stream'? – CKing

+0

Non è sempre così facile. Questo è quello che ho cercato di spiegare. Se l'operazione include tipi di dati primitivi e l'operazione può essere espressa come un'operazione di flusso senza boxe, è preferibile. Se l'operazione consiste di più passaggi o una combinazione di funzioni, l'API dello stream è probabilmente la strada da percorrere. Se l'origine dati è una raccolta o un array esistente e l'operazione prevista può essere espressa come una singola chiamata di un metodo esistente (basato su raccolta), utilizzare tale metodo. Se si desidera modificare la raccolta sul posto, rimanere nell'API di raccolta. – Holger