Возврат из лямбды forEach () в java

100

Я пытаюсь изменить некоторые циклы for-each на лямбда- 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()метода?

Самутамм
источник
Я еще не слишком хорошо знаком с внутренним устройством лямбд, но когда я задаю себе вопрос: «От чего бы вы вернулись?», Я изначально подозревал, что это не метод.
Gimby
1
@Gimby Да, returnвнутри оператора лямбда возвращается из самой лямбды, а не из того, что называется лямбда. Досрочное прекращение потока («короткое замыкание») используется, findFirstкак показано в ответе Яна Робертса .
Стюарт Маркс

Ответы:

122

Это returnвозвращается из лямбда-выражения, а не из содержащего метода. Вместо forEachстрима нужно filter:

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

Здесь filterпоток ограничивается теми элементами, которые соответствуют предикату, а findFirstзатем возвращается Optionalс первой совпадающей записью.

Это выглядит менее эффективным, чем подход цикла for, но на самом деле findFirst()может привести к короткому замыканию - он не генерирует весь отфильтрованный поток, а затем извлекает из него один элемент, а фильтрует только столько элементов, сколько необходимо, чтобы найти первый подходящий. Вы также можете использовать findAny()вместо этого, findFirst()если вам не обязательно заботиться о получении первого совпадающего игрока из (упорядоченного) потока, а просто о любом соответствующем элементе. Это позволяет повысить эффективность при использовании параллелизма.

Ян Робертс
источник
Спасибо, это то, что я искал! Кажется, есть много нового в Java8 для изучения :)
samutamm
10
Разумно, но я предлагаю вам не использовать orElse(null)на Optional. Суть в Optionalтом, чтобы предоставить способ указать наличие или отсутствие значения вместо перегрузки null (что приводит к NPE). Если вы optional.orElse(null)его используете, он выкупает все проблемы с нулями. Я бы использовал его, только если вы не можете изменить вызывающего, и он действительно ожидает нуль.
Стюарт Маркс
1
@StuartMarks действительно, изменение типа возвращаемого метода Optional<Player>было бы более естественным способом вписаться в парадигму потоков. Я просто пытался показать, как продублировать существующее поведение с помощью лямбда-выражений.
Ян Робертс
for (Часть часть: части) 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>более ArrayList<E>.
  2. Используйте алмазный вывод для параметра типа в new ArrayList<>(), в конце концов, вы используете Java 8.

А теперь перейдем ко второму пункту:

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

Скиви
источник
6

Если вы хотите вернуть логическое значение, вы можете использовать что-то вроде этого (намного быстрее, чем фильтр):

players.stream().anyMatch(player -> player.getName().contains(name));
Шри
источник
1

Вы также можете создать исключение:

Заметка:

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

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

если ваша логика слабо «управляется исключениями», например, в вашем коде есть одно место, которое улавливает все исключения и решает, что делать дальше. Используйте разработку, управляемую исключениями, только тогда, когда вы можете избежать засорения своей базы кода множественными числами, try-catchи выброс этих исключений предназначен для очень особых случаев, которые вы ожидаете от них и с которыми можно правильно справиться.)

набстер
источник