Чому я повинен використовувати «функціональні операції» замість циклу?


39
for (Canvas canvas : list) {
}

NetBeans пропонує мені використовувати "функціональні операції":

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

Але чому це віддається перевазі ? Якщо що-небудь, важче читати і розуміти. Ви дзвоните stream(), forEach()використовуючи лямбда-вираз із параметром canvas. Я не бачу, як це краще, ніж forцикл у першому фрагменті.

Очевидно, я кажу лише з естетики. Можливо, тут є технічна перевага, яку мені не вистачає. Що це? Чому я повинен використовувати другий метод замість цього?



15
У вашому конкретному прикладі це не було б кращим.
Роберт Харві

1
Поки єдиною операцією є єдиний для кожного, я схильний погоджуватися з вами. Як тільки ви додаєте інші операції до трубопроводу або виробляєте вихідну послідовність, тоді потоковий підхід стає кращим.
ЖакБ

@RobertHarvey чи не так? чому ні?
сара

@RobertHarvey добре, я погоджуюсь, що прийнята відповідь дійсно показує, як фор-версія виривається з води для більш складних випадків, але я не бачу, чому "перемагає" у тривіальному випадку. ви заявляєте, що це зрозуміло, але я цього не бачу, тому я запитав.
сара

Відповіді:


45

Потоки забезпечують набагато кращу абстракцію для композицій різних операцій, які ви хочете робити над колекціями або потоками даних, що надходять. Особливо, коли вам потрібно зіставити елементи, відфільтрувати та перетворити їх.

Ваш приклад не дуже практичний. Розглянемо наступний код з сайту 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());
}

можна записати за допомогою потоків:

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

Другий варіант набагато читабельніший. Тож, коли ви вклали петлі або різні цикли, які роблять часткову обробку, це дуже хороший кандидат для використання API Streams / Lambda.


5
Існують такі методи оптимізації, як побудова / зменшення злиття карти ( stream.map(f).map(g)stream.map(f.andThen(g))) (коли будується потік одним методом, а потім передається іншому методу, який споживає його, компілятор може усунути потік) та синтез потоку (який може сплавити багато оперативних потоків разом в єдиний імперативний цикл), що може зробити потокові операції набагато ефективнішими. Вони реалізовані в компіляторі GHC Haskell, а також в деяких інших компіляторах Haskell та інших функціональних мов, а для Scala існують експериментальні дослідження.
Йорг W Міттаг

Продуктивність, ймовірно, не є фактором при розгляді функціонального проти циклу, оскільки, безумовно, компілятор міг / повинен здійснити перетворення з циклу for у функціональну операцію, якщо Netbeans може, і це визначено як оптимальний шлях.
Райан

Я б не погодився з тим, що друге є більш зрозумілим. Потрібен час, щоб з’ясувати, що відбувається. Чи є перевага для продуктивності цього методу другим методом, оскільки в іншому випадку я цього не бачу?
Бок Макдонах

@BokMcDonagh, досвід мене полягає в тому, що розробникам, які не намагаються ознайомитись з новими абстракціями, він менш читаний. Я б запропонував більше використовувати такі API, щоб ознайомитися більше, адже це майбутнє. Не тільки в світі Java.
luboskrnac

16

Ще одна перевага використання API функціонального потоку полягає в тому, що він приховує деталі реалізації. Він лише описує, що слід робити, а не як. Ця перевага стає очевидною при перегляді змін, які необхідно здійснити, щоб перейти від однопотокового до паралельного виконання коду. Просто змініть .stream()на .parallelStream().


13

Якщо що-небудь, важче читати і розуміти.

Це дуже суб'єктивно. Другу версію мені здається набагато легшою для читання та розуміння. Це відповідає тому, як це роблять інші мови (наприклад, Ruby, Smalltalk, Clojure, Io, Ioke, Seph), для розуміння цього потрібно менше понять (це просто звичайний виклик методу, як і будь-який інший, тоді як перший приклад - спеціалізований синтаксис).

Якщо що, це питання знайомства.


6
Так, це правда. Але це більше схоже на коментар, ніж на відповідь.
Омега

Функціональна операція також використовує спеціалізований синтаксис: "->" є новим
Райан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.