Warum sollte ich "funktionale Operationen" anstelle einer for-Schleife verwenden?

39
for (Canvas canvas : list) {
}

NetBeans schlägt mir vor, "funktionale Operationen" zu verwenden:

list.stream().forEach((canvas) -> {
});

Aber warum wird das bevorzugt ? Wenn überhaupt, ist es schwieriger zu lesen und zu verstehen. Sie rufen auf stream()und verwenden dann forEach()einen Lambda-Ausdruck mit Parameter canvas. Ich sehe nicht, wie das schöner ist als die forSchleife im ersten Snippet.

Offensichtlich spreche ich nur aus ästhetischen Gründen. Vielleicht gibt es hier einen technischen Vorteil, den ich vermisse. Was ist es? Warum sollte ich stattdessen die zweite Methode verwenden?

Omega
quelle
15
In Ihrem speziellen Beispiel wäre dies nicht bevorzugt.
Robert Harvey
1
Solange die einzige Operation für jeden Einzelnen ist, stimme ich Ihnen eher zu. Sobald Sie der Pipeline andere Operationen hinzufügen oder eine Ausgabesequenz erstellen, ist der Stream-Ansatz vorzuziehen.
JacquesB
@RobertHarvey, nicht wahr? warum nicht?
Sara
@RobertHarvey Nun, ich bin damit einverstanden, dass die akzeptierte Antwort wirklich zeigt, wie die For-Version in komplizierteren Fällen aus dem Wasser gesprengt wird, aber ich verstehe nicht, warum für "Gewinne" im trivialen Fall. Sie sagen, es sei selbstverständlich, aber ich sehe es nicht, also fragte ich.
Sara

Antworten:

45

Streams bieten eine viel bessere Abstraktion für die Komposition verschiedener Operationen, die Sie zusätzlich zu den eingehenden Sammlungen oder Streams von Daten ausführen möchten. Insbesondere, wenn Sie Elemente zuordnen, filtern und konvertieren müssen.

Ihr Beispiel ist nicht sehr praktisch. Betrachten Sie den folgenden Code von der Oracle-Site .

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

kann mit Streams geschrieben werden:

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Die zweite Option ist viel besser lesbar. Wenn Sie also verschachtelte Schleifen oder verschiedene Schleifen haben, die eine teilweise Verarbeitung durchführen, ist dies ein sehr guter Kandidat für die Verwendung der Streams / Lambda-API.

luboskrnac
quelle
5
Es gibt Optimierungstechniken wie Map Fusion ( stream.map(f).map(g)stream.map(f.andThen(g))), Build / Reduction Fusion (wenn ein Stream in einer Methode erstellt und dann an eine andere Methode übergeben wird, die ihn verbraucht, kann der Compiler den Stream eliminieren) und Stream Fusion (die viele Stream Ops verschmelzen kann) zusammen in einer einzigen imperativen Schleife), wodurch Stream-Vorgänge viel effizienter werden können. Sie sind im GHC-Haskell-Compiler sowie in einigen anderen Haskell- und anderen Compilern für funktionale Sprachen implementiert, und es gibt experimentelle Forschungsimplementierungen für Scala.
Jörg W Mittag
Die Leistung spielt wahrscheinlich keine Rolle, wenn Sie Functional vs Loop in Betracht ziehen, da der Compiler mit Sicherheit die Konvertierung von einer for-Schleife in eine funktionale Operation durchführen könnte / sollte, wenn Netbeans dies kann, und es wird als optimaler Pfad ermittelt.
Ryan
Ich würde nicht zustimmen, dass die zweite besser lesbar ist. Es dauert eine Weile, um herauszufinden, was los ist. Gibt es einen Leistungsvorteil bei der zweiten Methode, weil ich ihn sonst nicht sehe?
Bok McDonagh
@BokMcDonagh, Ich habe die Erfahrung gemacht, dass es für Entwickler, die sich nicht mit neuen Abstraktionen vertraut gemacht haben, weniger lesbar ist. Ich würde vorschlagen, solche APIs häufiger zu verwenden, um sich besser vertraut zu machen, da dies die Zukunft ist. Nicht nur in der Java-Welt.
Luboskrnac
16

Ein weiterer Vorteil der Verwendung der funktionalen Streaming-API besteht darin, dass Implementierungsdetails ausgeblendet werden. Es wird nur beschrieben, was und nicht wie zu tun ist. Dieser Vorteil wird deutlich, wenn man sich die Änderung ansieht, die vorgenommen werden muss, um von der Ausführung mit einem Thread zur Ausführung mit parallelem Code zu wechseln. Ändern Sie einfach die .stream()zu .parallelStream().

Stefan Dollase
quelle
13

Wenn überhaupt, ist es schwieriger zu lesen und zu verstehen.

Das ist sehr subjektiv. Ich finde die zweite Version viel einfacher zu lesen und zu verstehen. Es passt dazu, wie andere Sprachen (z. B. Ruby, Smalltalk, Clojure, Io, Ioke, Seph) es tun, es erfordert weniger Konzepte zum Verstehen (es ist nur ein normaler Methodenaufruf wie jeder andere, während das erste Beispiel eine spezialisierte Syntax ist).

Wenn überhaupt, ist es eine Frage der Vertrautheit.

Jörg W. Mittag
quelle
6
Ja, das ist wahr. Aber das sieht für mich eher nach einem Kommentar als nach einer Antwort aus.
Omega
Die Funktionsoperation verwendet ebenfalls eine spezielle Syntax: Das "->" ist neu
Ryan