2015-05-14 8 views
19

Ich lese Java 8, speziell die "Streams API". Ich wollte wissen, wie Streams faul sein können?Wie werden Lazy Streams in Java 8 implementiert?

Ich glaube, Streams sind nur als eine Bibliothek hinzugefügt und es gibt keine Änderungen in der Sprache Faulheit zu unterstützen. Außerdem werde ich schockiert sein, wenn mir jemand sagt, dass es durch Reflexion erreicht wird.

+0

Ich akzeptierte Alexis Antwort, weil es eine bessere Vorstellung davon gab, wonach ich suchte. Aber die Antwort von HuStmpHrrr sieht raffinierter aus. Danke allen. – Learner

Antwort

20

Warum brauchen Sie Reflexion, um Faulheit zu bekommen? Betrachten wir zum Beispiel diese Klasse:

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

Wenn Sie filter auf den faulen seq nennen, wird die Filterung nicht sofort passieren, so zum Beispiel:

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

Wenn Sie sehen den Inhalt der Sequenz nach dem Aufruf Filter, Sie werden sehen, dass es immer 1, 2, 3, 4 ist. Wenn Sie jedoch eine Terminaloperation aufrufen, wie z. B. forEach, wird die Filterung vor der Verwendung des Verbrauchers durchgeführt. So zum Beispiel:

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

2 gedruckt werden und 4.

Dies ist das gleiche Prinzip mit Stream s. Von einer Quelle führen Sie Operationen aus, die bestimmte Eigenschaften aufweisen. Diese Operationen sind entweder intermediär und geben einen verzögerten Stream zurück (z. B. filter oder map) oder terminal (z. B. forEach). Einige dieser terminalen Operationen sind kurzgeschlossen (wie findFirst), sodass Sie möglicherweise nicht die gesamte Pipeline durchlaufen (Sie können sich eine frühe Rückgabe in einer for-Schleife vorstellen, die beispielsweise den Index eines Werts in einem Array zurückgibt).

Wenn Sie eine Terminaloperation aufrufen, wird diese Operationskette ausgeführt, sodass Sie am Ende das erwartete Ergebnis erhalten.

Faulheit kann erreicht werden, indem ein neuer Zustand in der Pipeline gespeichert wird, wenn eine Zwischenoperation angewendet wird, und wenn Sie eine Terminaloperation aufrufen, werden alle Zustände nacheinander nach den Daten sortiert.

Die Stream-API ist nicht wirklich so implementiert (es ist ein bisschen komplexer), aber das Prinzip ist wirklich hier.

6

Keine Reflexion oder Proxies. Reflection und Proxies haben einen Performance-Preis, der vermieden werden sollte, es sei denn, es gibt keine Alternative und die Performance ist die Nummer eins in Java.

Was Faulheit ermöglicht, ist die funktionale Art, Dinge zu tun. Grundsätzlich beginnt eine stream mit einer Quelle (zB: Liste), Anzahl der Zwischenoperationen (zB: Filter, Map ..), und einer Terminaloperation (zB: count, sum, etc ..). Zwischenschritte werden langsam ausgeführt, da Sie Funktionen (lambdas) übergeben, die in der Pipeline verkettet werden und im Terminalschritt ausgeführt werden.

ex: filter(Predicate<? super T>) 

filter in diesem Beispiel erwartet, dass eine Funktion, die uns, ob ein Objekt im Stream erzählt einige Kriterien oder nicht erfüllt.

Viele Funktionen, die von Java 7 stammen, wurden verwendet, um dies effizient zu machen. Bsp: dynamic zum Ausführen von lambdas anstelle von Proxies oder anonymen inneren Klassen und ForkJoin Pools für die parallele Ausführung aufrufen.

Wenn Sie an Java 8 Interna interessiert sind, dann müssen Sie dieses Gespräch von dem Experten auf dem Gebiet Brian Goetz gegeben, es ist auf Youtube.

3

Streaming ist kein Container von Daten, sondern ein Container von Logik. Sie müssen nur eine Instanz der Schnittstelle übergeben, um sich an die Logik zu erinnern.

betrachten folgenden Code:

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

die Logik innerhalb von pred gewickelt, sondern nur aufgerufen, wenn das Objekt wiederholt wird. und tatsächlich speichert diese Klasse keine Daten, sie hält nur ein iterables, das Daten enthalten kann, oder sogar nur einen anderen logischen Halter selbst.

und Elemente werden auf Anfrage auch zurückgegeben. Diese Art von Paradigma macht so genannte Stream api faul.