2015-06-10 27 views
7

das folgende Beispiel betrachtet, das in einem List das maximale Element druckt:Die Wahl zwischen Strom und Sammlungen API

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

Das gleiche Ziel auch die Collections.max Methode erreicht werden kann:

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

Die oben Code ist nicht nur kürzer, sondern auch sauberer zu lesen (meiner Meinung nach). Es gibt ähnliche Beispiele, die in den Sinn kommen, wie die Verwendung von binarySearch gegenüber filter, die in Verbindung mit findAny verwendet wird.

Ich verstehe, dass Stream eine unendliche Pipeline im Gegensatz zu Collection sein kann, die durch den verfügbaren Speicher für die JVM begrenzt ist. Dies wäre mein Kriterium für die Entscheidung, ob eine Stream oder die Collections API verwendet werden soll. Gibt es andere Gründe für die Wahl Stream über die Collections API (z. B. Leistung). Allgemeiner, ist dies der einzige Grund, Stream über ältere API zu wählen, die die Arbeit auf eine sauberere und kürzere Weise erledigen kann?

+0

Fragen Sie nur nach dieser spezifischen Methode? –

+0

Wenn Ihr einziger Wert darin besteht, den 'max' -Wert von der Liste zu bekommen, ist die' Collections'-Methode die richtige Wahl. – GriffeyDog

+0

Auch das 'Optional' vs' NoSuchElementException' Verhalten. –

Antwort

5

Stream API ist wie ein Schweizer Taschenmesser: Es erlaubt Ihnen, ziemlich komplexe Operationen zu machen, indem Sie die Werkzeuge effektiv kombinieren. Auf der anderen Seite, wenn Sie nur einen Schraubenzieher benötigen, wäre wahrscheinlich der Standalone-Schraubendreher bequemer. Die Stream-API enthält viele Dinge (wie distinct, sorted, primitive Operationen usw.), die sonst erfordern würden, dass Sie mehrere Zeilen schreiben und Zwischenvariablen/Datenstrukturen und langweilige Schleifen einführen, die die Aufmerksamkeit des Programmierers vom eigentlichen Algorithmus ziehen. Manchmal kann die Verwendung der Stream-API die Leistung sogar für sequenziellen Code verbessern. Betrachten Sie zum Beispiel einige alte API:

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

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

Hier möchten wir alle Benutzer der Gruppe zurückgeben. Der API-Designer entschied sich, eine List zurückzugeben. Aber es kann draußen in einem verschiedenen Arten verwendet werden:

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

Hier ist es sortiert und umgewandelt Array zu einer anderen Methode zu übergeben, das passiert ist, ein Array zu akzeptieren. Im anderen Ort getUsers() kann wie folgt verwendet werden:

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

hier nur wollen wir den Benutzer einige Kriterien abgestimmt. In beiden Fällen war das Kopieren auf das Zwischenprodukt ArrayList eigentlich unnötig.

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

und ändern Sie den Anrufer Code: Wenn wir in Java 8 zu bewegen, können wir getUsers() Methode mit users() ersetzen. Die erste:

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

Die zweite:

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

Auf diese Weise ist es nicht nur kürzer, aber schneller als gut arbeiten können, weil wir die Zwischen Kopieren überspringen.

Der andere konzeptionelle Punkt in der Stream-API ist, dass jeder gemäß den Richtlinien geschriebene Stream-Code parallelisiert werden kann, indem einfach der Schritt parallel() hinzugefügt wird. Natürlich wird dies nicht immer die Leistung steigern, aber es hilft öfter als ich erwartet habe.Wenn die Operation sequentiell für 0.1ms or longer ausgeführt wird, kann es normalerweise von der Parallelisierung profitieren. Wie auch immer, wir haben noch nie eine so einfache Möglichkeit gesehen, die parallele Programmierung in Java zu machen.

2

Natürlich hängt es immer von den Umständen ab. Nehmen Sie erste Beispiel:

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

Wenn Sie die gleiche Sache effizient tun möchten, können Sie

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

verwenden würde, die keine Auto-Boxen beinhaltet. Aber wenn Ihre Annahme ist, eine List<Integer> im Voraus zu haben, könnte das keine Option sein, also, wenn Sie nur an der max Wert interessiert sind, könnte Collections.max die einfachere Wahl sein.

Aber das würde zu der Frage führen, warum Sie vorher eine List<Integer> haben. Vielleicht ist es das Ergebnis von altem Code (oder neuem Code, der mit altem Denken geschrieben wurde), der keine andere Wahl hatte, als mit Boxen und Collection s, da es in der Vergangenheit keine Alternative gab?

Also sollten Sie vielleicht über die Quelle nachdenken, die die Sammlung produziert, bevor Sie sich darum kümmern, wie Sie sie konsumieren (oder gut, denken Sie an beide gleichzeitig).

Wenn alles, was Sie haben ein Collection ist und alles, was Sie brauchen, ist ein einzelner Terminalbetrieb für die eine einfache Collection basierte Implementierung existiert, können Sie es direkt mit dem Stream API ohne Mühe verwenden. Die API-Designer bestätigten diese Idee, indem sie Methoden wie forEach(…) der API Collection hinzufügten, statt darauf zu bestehen, dass alle Benutzer stream().forEach(…) verwenden. Und Collection.forEach(…) ist keine einfache Abkürzung für Collection.stream().forEach(…), in der Tat ist es bereits auf der abstrakteren Iterable Schnittstelle definiert, die sogar keine stream() Methode hat.

Btw., Sollten Sie den Unterschied zwischen Collections.binarySearch und Stream.filter/findAny verstehen. Ersteres erfordert die Sammlung sortiert und wenn diese Voraussetzung erfüllt ist, könnte die bessere Wahl sein. Aber wenn die Sammlung nicht sortiert ist, ist eine einfache lineare Suche effizienter als das Sortieren nur für eine einzelne Verwendung der binären Suche, um nicht von der Tatsache zu sprechen, dass die binäre Suche nur mit List funktioniert, während filter/findAny mit jedem Stream arbeitet Unterstützung jeder Art von Quellensammlung.

+0

Schlägst du vor, dass jeder neue Code, den ich fortan schreibe, keine "List" oder "Collection" verwenden sollte, sondern immer einen 'Stream'? – CKing

+0

Es ist nicht immer so einfach. Das habe ich versucht zu erklären. Wenn Ihre Operation primitive Datentypen enthält und die Operation als Stream-Operation ohne Boxen ausgedrückt werden kann, ist dies vorzuziehen. Wenn Ihre Operation aus mehreren Schritten oder einer Kombination von Funktionen besteht, ist die Stream-API wahrscheinlich der richtige Weg. Wenn Ihre Datenquelle eine vorhandene Sammlung oder ein Array ist und die beabsichtigte Operation als einzelner Aufruf einer vorhandenen (sammlungsbasierten) Methode ausgedrückt werden kann, verwenden Sie diese Methode. Wenn Sie die Sammlung direkt ändern möchten, bleiben Sie bei der Sammlungs-API. – Holger

Verwandte Themen