2015-05-14 14 views
19

Sto leggendo Java 8, in particolare "Streams API". Volevo sapere come i flussi possono essere pigri?Come sono implementati i flussi pigri in Java 8?

Credo che gli stream siano appena aggiunti come una libreria e non ci siano modifiche apportate alla lingua per supportare la pigrizia. Inoltre, sarò scioccato se qualcuno mi dice che è raggiunto attraverso la riflessione.

+0

Ho accettato la risposta di Alexi perché mi dava un'idea migliore di ciò che stavo cercando. Ma la risposta di HuStmpHrrr sembra più raffinata. Ringrazia tutti. – Learner

risposta

20

Perché dovresti avere bisogno di una riflessione per ottenere la pigrizia? Ad esempio, si consideri questa classe:

class LazySeq<T> { 

    private final List<T> list; 
    private Predicate<? super T> predicate; 

    public LazySeq(List<T> input) { 
     this.list = new ArrayList<>(input); 
    } 

    //Here you just store the predicate, but you don't perform a filtering 
    //You could also return a new LazySeq with a new state 
    public LazySeq<T> filter(Predicate<? super T> predicate) { 
     this.predicate = predicate; 
     return this; 
    } 

    public void forEach(Consumer<? super T> consumer){ 
     if(predicate == null) { 
      list.forEach(consumer); 
     } else { 
      for(T elem : list) { 
       if(predicate.test(elem)) { 
        consumer.accept(elem); 
       } 
      } 
     } 
    } 
} 

Quando si chiama filter sulla ss pigro, il filtraggio non avviene immediatamente in modo, ad esempio:

LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4)); 
lazySeq = lazySeq.filter(i -> i%2 == 0); 

Se si vede il contenuto della sequenza dopo la chiamata filtro, vedrai che è sempre 1, 2, 3, 4. Tuttavia, quando si chiama un'operazione terminale, ad esempio forEach, il filtro verrà eseguito prima di utilizzare il consumatore. Così, per esempio:

lazySeq.filter(i -> i%2 == 0).forEach(System.out::println); 

stamperà 2 e 4.

Questo è lo stesso principio con Stream s. Da una sorgente, si concatenano operazioni che hanno proprietà determinate. Queste operazioni sono intermedie, che restituisce un flusso lento (ad esempio filter o map) o un terminale (ad esempio forEach). Alcune di queste operazioni di terminale sono in cortocircuito (come ad esempio findFirst), quindi non si può attraversare tutta la pipeline (si può pensare ad un ritorno anticipato in un ciclo for che restituisce l'indice di un valore in una matrice per esempio).

Quando si chiama un'operazione terminale, questa catena di operazioni inizia ad essere eseguita in modo che alla fine si ottenga il risultato previsto.

La pigrizia può essere ottenuta memorizzando un nuovo stato sulla pipeline quando viene applicato un op intermedio e quando si chiama un terminale op, si passano tutti gli stati uno ad uno sui dati.

L'API Stream non è realmente implementata in questo modo (è un po 'più complessa) ma in realtà il principio è qui.

6

Nessun riflesso o proxy. La riflessione e i proxy hanno un costo di prestazioni che dovrebbe essere evitato a meno che non ci sia un'alternativa e la performance è la numero uno in Java.

Ciò che rende possibile la pigrizia è lo stile funzionale del fare le cose. Fondamentalmente un stream inizia con una fonte (es: Lista), il numero di operazioni intermedie (es: filtri, mappa ..), e un'operazione di terminale (es: conteggio, somma, ecc.). I passaggi intermedi vengono eseguiti pigramente perché si passano le funzioni (lambda) che vengono incatenate nella pipeline per essere eseguite nella fase terminale.

ex: filter(Predicate<? super T>) 

filter in questo esempio si aspetta una funzione che ci dice se un oggetto nel flusso soddisfi alcuni criteri o meno.

Un sacco di funzionalità che provengono da Java 7 sono state utilizzate per rendere questo efficiente. Esempio: richiama dinamico per l'esecuzione di lambdas anziché proxy o classi interne anonime e pool ForkJoin per l'esecuzione parallela.

Se sei interessato agli interni di Java 8, devi guardare questo discorso tenuto dall'esperto nel campo Brian Goetz, è il Youtube.

3

lo streaming non è un contenitore di dati, ma un contenitore di logica. devi solo passare in un'istanza di interfaccia per ricordare la logica.

considerare codice seguente:

class FilterIterable<T> implements Iterable<T> 
{ 
    private Iterable<? extends T> iter; 
    private Predicate<? super T> pred; 

    public FilterIterable(Iterable<? extends T> iter, Predicate<? super T> pred) { 
     this.iter = iter; 
     this.pred = pred; 
    } 

    public Iterator<T> iterator() { 
     return FilterIterator<T>(); 
    } 

    class FilterIterator<T> implements Iterator<T> 
    { 
     private Iterator<? extends T> iterator = iter.iterator(); 
     private T next = null; 

     FilterIterator() { 
      getNext(); 
     } 

     private void getNext() { 
      next = null; 
      while (iterator.hasNext()) { 
       T temp = iterator.next(); 
       if (pred.test(temp)) { 
        next = temp; 
        break; 
       } 
      } 
     } 

     public boolean hasNext() { return next != null; } 

     public T next() { 
      T temp = next; 
      getNext(); 
      return temp; 
     }  
    } 
} 

la logica è avvolto all'interno di pred, ma invocata soltanto quando l'oggetto viene iterato. e in effetti, questa classe non memorizza dati, mantiene solo un iterabile che può contenere dati, o anche solo un altro titolare logico stesso.

e gli elementi vengono restituiti anche su richiesta. questo tipo di paradigma rende pigro il cosiddetto stream api.

Problemi correlati