Przewodnik po Javie 8 forEach

1. Przegląd

Wprowadzona w Javie 8 pętla forEach zapewnia programistom nowy, zwięzły i interesujący sposób na iterację kolekcji .

W tym artykule zobaczymy, jak używać forEach z kolekcjami, jakiego rodzaju argumentu używa i jak ta pętla różni się od rozszerzonej pętli for .

Jeśli chcesz odświeżyć niektóre pojęcia dotyczące języka Java 8, mamy zbiór artykułów, które mogą Ci pomóc.

2. Podstawy forEach

W Javie interfejs Collection ma Iterable jako super interfejs - a począwszy od Java 8 ten interfejs ma nowy interfejs API:

void forEach(Consumer action)

Mówiąc najprościej, Javadoc statystyki forEach, który „wykonuje daną akcję dla każdego elementu Iterable, dopóki wszystkie elementy nie zostaną przetworzone lub akcja zgłosi wyjątek”.

I tak za pomocą forEach możemy iterować po kolekcji i wykonać daną akcję na każdym elemencie, tak jak każdy inny Iterator.

Na przykład, dla pętli wersja iteracji i drukowanie Collection of Strings :

for (String name : names) { System.out.println(name); }

Możemy to napisać używając forEach jako:

names.forEach(name -> { System.out.println(name); });

3. Korzystanie z metody forEach

Używamy forEach do iteracji kolekcji i wykonania określonej akcji na każdym elemencie. Czynność do wykonania zawarta jest w klasie implementującej interfejs Consumer i jest przekazywana do forEach jako argument.

Konsumentów interfejsem jest interfejs funkcjonalna (interfejs z jednej metody streszczenie). Przyjmuje dane wejściowe i nie zwraca żadnego wyniku.

Oto definicja:

@FunctionalInterface public interface Consumer { void accept(T t); }

Dlatego każda implementacja, na przykład konsument, która po prostu drukuje String :

Consumer printConsumer = new Consumer() { public void accept(String name) { System.out.println(name); }; };

można przekazać forEach jako argument:

names.forEach(printConsumer);

Ale to nie jedyny sposób tworzenia akcji przez konsumenta i wykorzystanie forEach API.

Przyjrzyjmy się 3 najpopularniejszym sposobom wykorzystania metody forEach :

3.1. Anonimowa implementacja konsumencka

Możemy utworzyć instancję interfejsu Consumer za pomocą anonimowej klasy, a następnie zastosować ją jako argument do metody forEach :

Consumer printConsumer= new Consumer() { public void accept(String name) { System.out.println(name); } }; names.forEach(printConsumer);

Działa to dobrze, ale jeśli przeanalizujemy powyższy przykład, zobaczymy, że rzeczywistą częścią, która jest używana, jest kod wewnątrz metody accept () .

Chociaż wyrażenia lambda są teraz normą i łatwiejszym sposobem, aby to zrobić, nadal warto wiedzieć, jak zaimplementować interfejs konsumenta .

3.2. Wyrażenie lambda

Główną zaletą interfejsów funkcjonalnych Java 8 jest to, że możemy używać wyrażeń Lambda do tworzenia ich instancji i unikać stosowania nieporęcznych implementacji klas anonimowych.

Ponieważ Consumer Interface jest interfejsem funkcjonalnym, możemy to wyrazić w Lambdzie w postaci:

(argument) -> { //body }

Dlatego nasz printConsumer upraszcza:

name -> System.out.println(name)

I możemy przekazać to forEach jako:

names.forEach(name -> System.out.println(name));

Od czasu wprowadzenia wyrażeń Lambda w Javie 8 jest to prawdopodobnie najpowszechniejszy sposób używania metody forEach .

Lambdy mają bardzo realną krzywą uczenia się, więc jeśli zaczynasz, ten artykuł omawia kilka dobrych praktyk pracy z nową funkcją językową.

3.3. Odniesienie do metody

Możemy użyć składni odwołania do metody zamiast normalnej składni Lambda, w której metoda już istnieje, aby wykonać operację na klasie:

names.forEach(System.out::println);

4. Praca z forEach

4.1. Iteracja po kolekcji

Dowolna iteracja typu Collection - lista, zestaw, kolejka itp. Mają taką samą składnię jak forEach.

Dlatego, jak już widzieliśmy, aby powtórzyć elementy listy:

List names = Arrays.asList("Larry", "Steve", "James"); names.forEach(System.out::println);

Podobnie dla zestawu:

Set uniqueNames = new HashSet(Arrays.asList("Larry", "Steve", "James")); uniqueNames.forEach(System.out::println);

Albo powiedzmy w przypadku kolejki, która jest również zbiorem :

Queue namesQueue = new ArrayDeque(Arrays.asList("Larry", "Steve", "James")); namesQueue.forEach(System.out::println);

4.2. Iterowanie po mapie - używanie map forEach

Mapy nie są iterowalne , ale zapewniają własny wariant forEach, który akceptuje BiConsumer .

BiConsumer został wprowadzony zamiast Konsumenta w iterable za forEach tak, że działanie może być wykonywane zarówno na klucz i wartość mapą jednocześnie.

Stwórzmy Mapę zawierającą wpisy:

Map namesMap = new HashMap(); namesMap.put(1, "Larry"); namesMap.put(2, "Steve"); namesMap.put(3, "James");

Następna, niech iteracyjnego namesMap używając mapy za foreach :

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

Jak widać tutaj, użyliśmy BiConsumer :

(key, value) -> System.out.println(key + " " + value)

do iteracji po wpisach mapy .

4.3. Iterowanie po mapie - przez iterację entrySet

Możemy również iteracyjne EntrySet o Mapie użyciu iterowalny za foreach.

Ponieważ wpisy mapy są przechowywane w zestawie o nazwie EntrySet, możemy to iterować za pomocą forEach:

namesMap.entrySet().forEach(entry -> System.out.println( entry.getKey() + " " + entry.getValue()));

5. Foreach vs For-Loop

Z prostego punktu widzenia obie pętle zapewniają tę samą funkcjonalność - pętlę przechodzącą przez elementy w kolekcji.

Główna różnica między nimi polega na tym, że są to różne iteratory - ulepszona pętla for jest iteratorem zewnętrznym, podczas gdy nowa metoda forEach jest iteratorem wewnętrznym .

5.1. Iterator wewnętrzny - forEach

Ten typ iteratora zarządza iteracją w tle i pozostawia programiście tylko kodowanie tego, co ma być zrobione z elementami kolekcji.

Zamiast tego iterator zarządza iteracją i zapewnia przetwarzanie elementów jeden po drugim.

Zobaczmy przykład wewnętrznego iteratora:

names.forEach(name -> System.out.println(name));

W powyższej metodzie forEach widzimy, że podany argument jest wyrażeniem lambda. Oznacza to, że metoda musi tylko wiedzieć, co należy zrobić, a cała praca związana z iteracją zostanie wykonana wewnętrznie.

5.2. Zewnętrzny Iterator - pętla for

Zewnętrzne iteratory mieszają co i jak pętla ma zostać wykonana.

Wyliczenia , Iteratory i ulepszona pętla for są zewnętrznymi iteratorami (pamiętasz metody iterator (), next () czy hasNext () ?). We wszystkich tych iteratorach naszym zadaniem jest określenie sposobu wykonywania iteracji.

Rozważ tę znajomą pętlę:

for (String name : names) { System.out.println(name); }

Chociaż nie wywołujemy jawnie metod hasNext () lub next () podczas iteracji po liście, podstawowy kod, który powoduje tę iterację, używa tych metod. Oznacza to, że złożoność tych operacji jest ukryta przed programistą, ale nadal istnieje.

W przeciwieństwie do wewnętrznego iteratora, w którym kolekcja sama wykonuje iterację, tutaj wymagamy zewnętrznego kodu, który pobiera każdy element z kolekcji.

6. Wniosek

W tym artykule pokazaliśmy, że pętla forEach jest wygodniejsza niż zwykła pętla for .

Zobaczyliśmy również, jak działa metoda forEach i jakiego rodzaju implementację można otrzymać jako argument, aby wykonać akcję na każdym elemencie kolekcji.

Wreszcie, wszystkie fragmenty użyte w tym artykule są dostępne w naszym repozytorium Github.