Mengapa saya harus menggunakan "operasi fungsional" alih-alih for loop?

39
for (Canvas canvas : list) {
}

NetBeans menyarankan saya untuk menggunakan "operasi fungsional":

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

Tetapi mengapa ini lebih disukai ? Jika ada, lebih sulit untuk dibaca dan dipahami. Anda menelepon stream(), lalu forEach()menggunakan ekspresi lambda dengan parameter canvas. Saya tidak melihat bagaimana itu lebih bagus daripada forloop di cuplikan pertama.

Jelas saya berbicara tentang estetika saja. Mungkin ada keuntungan teknis di sini yang saya lewatkan. Apa itu? Mengapa saya harus menggunakan metode kedua?

Akhir
sumber
15
Dalam contoh khusus Anda, itu tidak akan disukai.
Robert Harvey
1
Selama satu-satunya operasi adalah satu untuk setiap orang, saya cenderung setuju dengan Anda. Segera setelah Anda menambahkan operasi lain ke pipa, atau Anda menghasilkan urutan output, maka pendekatan aliran menjadi lebih disukai.
JacquesB
@RobertHarvey bukan? kenapa tidak?
sara
@RobertHarvey, saya setuju dengan jawaban yang diterima benar-benar menunjukkan bagaimana versi untuk ditiup keluar dari air untuk kasus yang lebih rumit, tapi saya tidak melihat mengapa untuk "menang" dalam kasus sepele. Anda menyatakannya seperti itu sudah jelas tapi saya tidak melihatnya, jadi saya bertanya.
sara

Jawaban:

45

Streaming menyediakan abstraksi yang jauh lebih baik untuk komposisi operasi berbeda yang ingin Anda lakukan di atas koleksi atau aliran data yang masuk. Terutama ketika Anda perlu memetakan elemen, memfilter dan mengubahnya.

Contoh Anda sangat tidak praktis. Pertimbangkan kode berikut dari situs Oracle .

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

dapat ditulis menggunakan stream:

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

Opsi kedua jauh lebih mudah dibaca. Jadi, ketika Anda memiliki loop bersarang atau berbagai loop melakukan pemrosesan parsial, itu kandidat yang sangat baik untuk penggunaan API Streams / Lambda.

luboskrnac
sumber
5
Ada teknik optimasi seperti map fusion ( stream.map(f).map(g)stream.map(f.andThen(g))) build / pengurangan fusi (ketika membangun aliran dalam satu metode dan kemudian meneruskannya ke metode lain yang mengkonsumsinya, kompiler dapat menghilangkan aliran) dan aliran fusi (yang dapat melebur banyak aliran ops bersama-sama menjadi satu loop imperatif), yang dapat membuat operasi aliran jauh lebih efisien. Mereka diimplementasikan dalam kompiler GHK Haskell, dan juga di beberapa Haskell lainnya dan kompiler bahasa fungsional lainnya, dan ada implementasi penelitian eksperimental untuk Scala.
Jörg W Mittag
Kinerja mungkin bukan faktor ketika mempertimbangkan fungsional vs loop karena pasti kompiler dapat / harus melakukan konversi dari for loop ke operasi fungsional jika Netbeans dapat dan ditentukan sebagai jalur optimal.
Ryan
Saya tidak setuju bahwa yang kedua lebih mudah dibaca. Butuh beberapa saat untuk mencari tahu apa yang terjadi. Apakah ada keuntungan kinerja untuk melakukannya metode kedua karena kalau tidak saya tidak melihatnya?
Bok McDonagh
@BokMcDonagh, pengalaman Me adalah bahwa itu kurang mudah dibaca untuk pengembang yang tidak repot-repot mengenal abstraksi baru. Saya akan menyarankan untuk lebih banyak menggunakan API seperti itu, untuk menjadi lebih akrab, karena ini adalah masa depan. Tidak hanya di dunia Jawa.
luboskrnac
16

Keuntungan lain menggunakan API streaming fungsional adalah, bahwa ia menyembunyikan detail implementasi. Itu hanya menggambarkan apa yang harus dilakukan, bukan bagaimana. Keuntungan ini menjadi jelas ketika melihat perubahan yang perlu dilakukan, untuk mengubah dari satu threaded ke eksekusi kode paralel. Ubah saja .stream()ke .parallelStream().

Stefan Dollase
sumber
13

Jika ada, lebih sulit untuk dibaca dan dipahami.

Itu sangat subyektif. Saya menemukan versi kedua lebih mudah dibaca dan dimengerti. Ini cocok dengan bagaimana bahasa lain (mis. Ruby, Smalltalk, Clojure, Io, Ioke, Seph) melakukannya, memerlukan lebih sedikit konsep untuk dipahami (itu hanya panggilan metode normal seperti yang lain, sedangkan contoh pertama adalah sintaksis khusus).

Jika ada, itu masalah keakraban.

Jörg W Mittag
sumber
6
Ya itu benar. Tapi ini lebih seperti komentar daripada jawaban untuk saya.
Omega
Operasi funtional juga menggunakan sintaksis khusus: "->" adalah baru
Ryan