Powrót z lambda forEach () w java

100

Próbuję zmienić niektóre pętle for-each na forEach()metody lambda, aby odkryć możliwości wyrażeń lambda. Wydaje się, że możliwe jest:

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

Z lambdą forEach()

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

Ale następny nie działa:

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

z lambdą

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

Czy coś jest nie tak w składni ostatniej linii, czy też nie można wrócić z forEach()metody?

samutamm
źródło
Nie jestem jeszcze zbyt zaznajomiony z wewnętrznymi właściwościami lambd, ale kiedy zadaję sobie pytanie: „Z czego wrócisz?”, Początkowo podejrzewam, że to nie jest metoda.
Gimby
1
@Gimby Tak, returnw instrukcji lambda wraca z samej lambdy, a nie z tego, co nazywa się lambdą. Kończy strumień na początku („zwarcie”), findFirstjak pokazano w odpowiedzi Iana Robertsa .
Stuart Marks

Odpowiedzi:

121

returnNie powraca z wyrażenia lambda zamiast ze sposobu zawierającego. Zamiast forEachciebie potrzebujesz filterstrumienia:

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

Tutaj filterogranicza strumień do tych elementów, które pasują do predykatu, a findFirstnastępnie zwraca Optionalz pierwszym pasującym wpisem.

Wygląda to na mniej wydajne niż podejście for-loop, ale w rzeczywistości findFirst()może powodować zwarcie - nie generuje całego przefiltrowanego strumienia, a następnie wyodrębnia z niego jeden element, a raczej filtruje tylko tyle elementów, ile potrzebuje, aby znajdź pierwszy pasujący. Możesz także użyć findAny()zamiast, findFirst()jeśli niekoniecznie zależy ci na uzyskaniu pierwszego pasującego gracza z (uporządkowanego) strumienia, ale po prostu dowolnego pasującego elementu. Pozwala to na lepszą wydajność, gdy występuje równoległość.

Ian Roberts
źródło
Dzięki, właśnie tego szukałem! Wydaje się, że jest wiele nowych rzeczy do odkrycia w Java8 :)
samutamm
10
Rozsądne, ale sugeruję, aby nie używać orElse(null)na Optional. Głównym celem Optionaljest zapewnienie sposobu wskazania obecności lub braku wartości zamiast przeciążania wartości null (co prowadzi do NPE). Jeśli optional.orElse(null)go użyjesz , odkupisz wszystkie problemy z wartościami null. Użyłbym go tylko wtedy, gdy nie możesz zmodyfikować dzwoniącego i naprawdę oczekuje wartości null.
Stuart Marks
1
@StuartMarks rzeczywiście, modyfikacja zwracanego typu metody na Optional<Player>byłaby bardziej naturalnym sposobem dopasowania do paradygmatu strumieni. Chciałem tylko pokazać, jak powielić istniejące zachowanie za pomocą lambd.
Ian Roberts
for (Część: części) if (! part.isEmpty ()) return false; Zastanawiam się, co jest naprawdę krótsze. I jaśniej. Api strumienia java naprawdę zniszczyło język i środowisko java. Koszmar do pracy w dowolnym projekcie Java w 2020 r.
mmm
17

Proponuję najpierw spróbować zrozumieć Java 8 w całości, w twoim przypadku najważniejsze będą strumienie, lambdy i odwołania do metod.

Nigdy nie należy konwertować istniejącego kodu do kodu Java 8 na podstawie linii, należy wyodrębniać funkcje i konwertować je.

To, co zidentyfikowałem w twoim pierwszym przypadku, jest następujące:

  • Chcesz dodać elementy struktury wejściowej do listy wyników, jeśli pasują do jakiegoś predykatu.

Zobaczmy, jak to robimy, możemy to zrobić w następujący sposób:

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

To, co tutaj robisz, to:

  1. Zamień swoją strukturę wejściową w strumień (zakładam, że jest to typ Collection<Player>, teraz masz plik Stream<Player>.
  2. Odfiltruj wszystkie niechciane elementy za pomocą Predicate<Player>, mapując każdego gracza na wartość logiczną true, jeśli chcesz ją zachować.
  3. Zbierz wynikowe elementy w listę, za pomocą a Collector, tutaj możemy użyć jednego ze standardowych kolektorów biblioteki, którym jest Collectors.toList().

Obejmuje to również dwa inne punkty:

  1. Koduj przeciwko interfejsom, więc koduj przeciwko List<E>over ArrayList<E>.
  2. Użyj wnioskowania diamentowego dla parametru typu w new ArrayList<>(), w końcu używasz Java 8.

Przejdźmy teraz do drugiego punktu:

Znowu chcesz przekonwertować coś ze starszej Javy na Javę 8 bez patrzenia na większy obraz. Na tę część odpowiedział już @IanRoberts , chociaż myślę, że musisz zrobić players.stream().filter(...)...to, co zasugerował.

skiwi
źródło
6

Jeśli chcesz zwrócić wartość logiczną, możesz użyć czegoś takiego (znacznie szybciej niż filtr):

players.stream().anyMatch(player -> player.getName().contains(name));
Sri
źródło
4

To mi pomogło:

List<RepositoryFile> fileList = response.getRepositoryFileList();
RepositoryFile file1 = fileList.stream().filter(f -> f.getName().contains("my-file.txt")).findFirst().orElse(null);

Zaczerpnięte z Java 8 Finding Specific Element in List with Lambda

user5495300
źródło
1

Możesz również zgłosić wyjątek:

Uwaga:

Ze względu na czytelność każdy krok strumienia powinien być wymieniony w nowej linii.

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

jeśli Twoja logika jest luźno „sterowana wyjątkami”, na przykład w kodzie jest jedno miejsce, które wyłapuje wszystkie wyjątki i decyduje, co dalej. Używaj programowania opartego na wyjątkach tylko wtedy, gdy możesz uniknąć zaśmiecania bazy kodu wielokrotnościami, try-catcha zgłaszanie tych wyjątków dotyczy bardzo specjalnych przypadków, których się spodziewasz i można je poprawnie obsłużyć).

nabster
źródło