Повернення з лямбди forEach () у java


97

Я намагаюся змінити деякі цикли для кожного на лямбда- forEach()методи, щоб виявити можливості лямбда-виразів. Можливо, можливо наступне:

ArrayList<Player> playersOfTeam = new ArrayList<Player>();      
for (Player player : players) {
    if (player.getTeam().equals(teamName)) {
        playersOfTeam.add(player);
    }
}

З лямбдою forEach()

players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});

Але наступний не працює:

for (Player player : players) {
    if (player.getName().contains(name)) {
        return player;
    }
}

з лямбдою

players.forEach(player->{if (player.getName().contains(name)) {return player;}});

Чи є щось не так у синтаксисі останнього рядка або неможливо повернутися з forEach()методу?


Я ще не надто знайомий з внутрішніми елементами лямбди, але коли я задаю собі питання: "З чого б ти повертався?", Я спочатку підозрював, що це не метод.
Гімбі,

1
@Gimby Так, returnу заяві лямбда повертається від самої лямбди, а не від того, що називається лямбда. Достроково припиніть потік ("коротке замикання"), findFirstяк показано у відповіді Яна Робертса .
Стюарт Марк

Відповіді:


121

returnЄ повертається з лямбда - вираження , а не з методу , що містить. Замість forEachвас потрібно filterв потік:

players.stream().filter(player -> player.getName().contains(name))
       .findFirst().orElse(null);

Тут filterобмежує потік тими елементами, які відповідають предикату, а findFirstпотім повертає a Optionalіз першим відповідним записом.

Це виглядає менш ефективним, ніж підхід for-loop, але насправді findFirst()може призвести до короткого замикання - воно не генерує весь відфільтрований потік, а потім витягує з нього один елемент, а фільтрує лише стільки елементів, скільки йому потрібно для того, щоб знайти перший відповідний. Ви також можете використовувати findAny()замість того, findFirst()якщо вам не обов’язково потрібен отримання першого відповідного гравця з (замовленого) потоку, а просто будь-який відповідний предмет. Це забезпечує кращу ефективність, коли йдеться про паралелізм.


Дякую, це те, що я шукав! Здається, в Java8 є багато нового для вивчення :)
samutamm

10
Розумно, але я пропоную вам не використовувати orElse(null)на Optional. Основним моментом Optionalє надання способу вказати наявність або відсутність значення замість перевантаження null (що призводить до NPE). Якщо ви використовуєте optional.orElse(null)це, викупляє всі проблеми з нулями. Я б використовував його, лише якщо ви не можете змінити абонента, і він справді очікує нуля.
Стюарт Марк

1
@StuartMarks справді, зміна типу повернення методу на Optional<Player>буде більш природним способом вписатися в парадигму потоків. Я просто намагався показати, як дублювати існуючу поведінку, використовуючи лямбди.
Ян Робертс,

для (Part part: parts) if (! part.isEmpty ()) return false; Цікаво, що насправді коротше. І чіткіше. API потоку Java по-справжньому знищив мову Java та середовище Java. Кошмар для роботи в будь-якому Java-проекті в 2020 році.
ммм,

17

Я пропоную вам спочатку спробувати зрозуміти Java 8 у цілому, головне у вашому випадку це будуть потоки, лямбди та посилання на методи.

Ти ніколи не повинен перетворювати існуючий код у код Java 8 по черзі, ви повинні витягувати функції та перетворювати їх.

У вашому першому випадку я визначив наступне:

  • Ви хочете додати елементи вхідної структури до списку вихідних даних, якщо вони відповідають якомусь предикату.

Давайте подивимося, як ми це робимо, ми можемо зробити це наступним чином:

List<Player> playersOfTeam = players.stream()
    .filter(player -> player.getTeam().equals(teamName))
    .collect(Collectors.toList());

Що ви тут робите:

  1. Перетворіть свою структуру введення в потік (я тут припускаю, що вона має тип Collection<Player>, тепер у вас є файл Stream<Player>.
  2. Відфільтруйте всі небажані елементи за допомогою a Predicate<Player>, зіставляючи кожного гравця з логічним значенням true, якщо його бажають зберегти.
  3. Зберіть отримані елементи у списку за допомогою a Collector, тут ми можемо використовувати один із стандартних колекторів бібліотек, яким є Collectors.toList().

Це також включає ще два моменти:

  1. Код проти інтерфейсів, тому код проти List<E>overArrayList<E> .
  2. Використовуйте алмазний умовивід для параметра типу в new ArrayList<>(), ви все-таки використовуєте Java 8.

Тепер про вашу другу точку:

Ви знову хочете перетворити щось із застарілої Java на Java 8, не дивлячись на загальну картину. На цю частину вже відповів @IanRoberts , хоча я думаю, що вам потрібно зробити players.stream().filter(...)...більше, ніж він запропонував.


5

Якщо ви хочете повернути логічне значення, ви можете використати щось подібне (набагато швидше, ніж фільтр):

players.stream().anyMatch(player -> player.getName().contains(name));


1

Ви також можете створити виняток:

Примітка:

Для читабельності кожен крок потоку повинен бути перелічений у новому рядку.

players.stream()
       .filter(player -> player.getName().contains(name))
       .findFirst()
       .orElseThrow(MyCustomRuntimeException::new);

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

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