I flussi limitati semplicemente non vengono creati tramite Stream.generate
.
Il modo standard di implementazione di uno stream consiste nell'implementare uno Spliterator
, talvolta utilizzando the Iterator
detour. In entrambi i casi, l'implementazione ha un modo per segnalare una fine, ad es. quando Spliterator.tryAdvance
restituisce false
o il suo metodo forEachRemaining
restituisce o, in caso di origine Iterator
, quando hasNext()
restituisce false
.
A Spliterator
può anche riportare il numero previsto di elementi prima dell'inizio dell'elaborazione.
Streams, create tramite uno dei metodi di fabbrica all'interno dell'interfaccia Stream
, come Stream.generate
possono essere eseguite sia, da un Spliterator
come bene o utilizzando le funzionalità interne di attuazione torrente, ma a prescindere da come loro applicazione, si don' t mettere le mani su questa implementazione per modificare il loro comportamento, quindi l'unico modo per rendere finito un flusso di questo tipo consiste nel concatenare un'operazione allo limit
allo stream.
Se si desidera creare un flusso finito non vuoto che non sia supportato da una matrice o da una raccolta e nessuna delle sorgenti di flusso esistenti si adatti, è necessario implementare il proprio Spliterator
e create a stream out of it. Come detto sopra, è possibile utilizzare un metodo esistente per creare un Spliterator
su un Iterator
, ma si dovrebbe resistere alla tentazione di utilizzare un Iterator
solo perché è familiare. Un Spliterator
non è difficile da implementare:
/** like {@code Stream.generate}, but with an intrinsic limit */
static <T> Stream<T> generate(Supplier<T> s, long count) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
long remaining=count;
public boolean tryAdvance(Consumer<? super T> action) {
if(remaining<=0) return false;
remaining--;
action.accept(s.get());
return true;
}
}, false);
}
Da questo punto di partenza, si può aggiungere sostituzioni per i default
metodi Spliterator
interfaccia, costi di sviluppo di pesi e potenziali miglioramenti delle prestazioni, ad esempio
static <T> Stream<T> generate(Supplier<T> s, long count) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
long remaining=count;
public boolean tryAdvance(Consumer<? super T> action) {
if(remaining<=0) return false;
remaining--;
action.accept(s.get());
return true;
}
/** May improve the performance of most non-short-circuiting operations */
@Override
public void forEachRemaining(Consumer<? super T> action) {
long toGo=remaining;
remaining=0;
for(; toGo>0; toGo--) action.accept(s.get());
}
}, false);
}
Perché evitare di utilizzare un iteratore per definire la spliterator? Ho appena visto che BufferedReader.lines() usa questo approccio per creare il suo flusso finito, per esempio. – Juru
'BufferedReader.lines()' è un buon esempio. Guarda l'implementazione di 'next()' e ['hasNext()'] (http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/io /BufferedReader.java?av = f # 566) e come devono mantenere lo stato tra le invocazioni. Al contrario, uno spliterator è straight-forward, è necessario un singolo metodo: 'tryAdvance (Consumer Super String> c) {String line = readLine(); if (line == null) restituisce false; c.accept (line); ritorna vero; } 'e questo è tutto. Più semplice da implementare (aggiungi la gestione delle eccezioni e ancora metà della dimensione del codice) e nessun wrapper necessario ... – Holger
Questa implementazione è sicura per i thread? Ha bisogno di essere? – WillD