Modyfikacja zmiennej lokalnej w programie forEach
powoduje błąd kompilacji:
Normalna
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
Z Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Masz jakiś pomysł, jak to rozwiązać?
Odpowiedzi:
Użyj opakowania
Każdy rodzaj opakowania jest dobry.
W przypadku języka Java 8+ użyj
AtomicInteger
:... lub tablica:
Z Javą 10+ :
Uwaga: zachowaj ostrożność, jeśli używasz strumienia równoległego. Możesz nie osiągnąć oczekiwanego rezultatu. Inne rozwiązania, takie jak Stuart, mogą być lepiej dostosowane do tych przypadków.
Dla typów innych niż
int
Oczywiście jest to nadal ważne dla typów innych niż
int
. Musisz tylko zmienić typ zawijania naAtomicReference
tablicę lub tablicę tego typu. Na przykład, jeśli używasz aString
, po prostu wykonaj następujące czynności:Użyj tablicy:
Lub z Javą 10+:
źródło
int[] ordinal = { 0 };
? Możesz to wyjaśnić. Dziękujęclass MyClass$1 { int value; }
W zwykłej klasie oznacza to, że tworzysz klasę z prywatną zmienną pakietu o nazwievalue
. To jest to samo, ale klasa jest w rzeczywistości anonimowa dla nas, a nie dla kompilatora lub JVM. Java skompiluje kod tak, jakby byłMyClass$1 wrapper = new MyClass$1();
. A klasa anonimowa staje się po prostu kolejną klasą. Na koniec dodaliśmy tylko cukier syntaktyczny, aby był czytelny. Ponadto klasa jest klasą wewnętrzną z polem pakiet-prywatny. To pole jest użyteczne.Jest to dość bliskie problemowi XY . Oznacza to, że zasadniczo zadaje się pytanie, jak zmutować przechwyconą zmienną lokalną z lambda. Ale aktualnym zadaniem jest numerowanie elementów listy.
Z mojego doświadczenia wynika, że w 80% przypadków w górę pojawia się pytanie, jak zmutować przechwycony lokalnie z wnętrza lambda, jest lepszy sposób postępowania. Zwykle wiąże się to z redukcją, ale w tym przypadku technika prowadzenia strumienia po indeksach list ma zastosowanie:
źródło
list
jestRandomAccess
listaJeśli potrzebujesz tylko przekazać wartość z zewnątrz do lambda, a nie ją wyciągać, możesz to zrobić za pomocą zwykłej klasy anonimowej zamiast lambdy:
źródło
Ponieważ użyte zmienne spoza lamdy muszą być (niejawnie) ostateczne, musisz użyć czegoś podobnego
AtomicInteger
lub napisać własną strukturę danych.Zobacz https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
źródło
Jeśli korzystasz z Java 10, możesz użyć
var
do tego:źródło
Alternatywą dla
AtomicInteger
(lub dowolnego innego obiektu, który może przechowywać wartość) jest użycie tablicy:Ale spójrz na odpowiedź Stuarta : może być lepszy sposób na załatwienie sprawy.
źródło
Wiem, że to stare pytanie, ale jeśli nie masz nic przeciwko obejściu, biorąc pod uwagę fakt, że zmienna zewnętrzna powinna być ostateczna, możesz po prostu zrobić to:
Może nie jest to najbardziej eleganckie, a nawet najbardziej poprawne, ale wystarczy.
źródło
Możesz to zakończyć, aby obejść kompilator, ale pamiętaj, że efekty uboczne w lambdach są odradzane.
Cytując plik javadoc
źródło
Miałem nieco inny problem. Zamiast zwiększać lokalną zmienną w forEach, musiałem przypisać obiekt do zmiennej lokalnej.
Rozwiązałem to, definiując prywatną klasę domeny wewnętrznej, która otacza zarówno listę, którą chcę iterować (countryList), jak i dane wyjściowe, które mam nadzieję uzyskać z tej listy (foundCountry). Następnie używając Java 8 „forEach”, iteruję po polu listy, a kiedy szukany obiekt zostanie znaleziony, przypisuję ten obiekt do pola wyjściowego. Więc to przypisuje wartość do pola zmiennej lokalnej, nie zmieniając samej zmiennej lokalnej. Uważam, że skoro sama zmienna lokalna nie jest zmieniana, kompilator nie narzeka. Następnie mogę użyć wartości, którą przechwyciłem w polu wyjściowym, poza listą.
Obiekt domeny:
Obiekt opakowania:
Operacja iteracyjna:
Możesz usunąć metodę klasy opakowania „setCountryList ()” i nadać polu „countryList” ostateczną wartość, ale nie otrzymałem błędów kompilacji, pozostawiając te szczegóły bez zmian.
źródło
Aby mieć bardziej ogólne rozwiązanie, możesz napisać ogólną klasę Wrapper:
(jest to wariant rozwiązania podany przez Almira Camposa).
W konkretnym przypadku nie jest to dobre rozwiązanie, ponieważ
Integer
jest gorsze niżint
w twoim celu, zresztą to rozwiązanie jest bardziej ogólne.źródło
Tak, możesz modyfikować zmienne lokalne z wnętrza lambd (w sposób pokazany przez inne odpowiedzi), ale nie powinieneś tego robić. Lambdy zostały stworzone dla funkcjonalnego stylu programowania, co oznacza: Brak efektów ubocznych. To, co chcesz robić, jest uważane za zły styl. Jest to również niebezpieczne w przypadku strumieni równoległych.
Powinieneś albo znaleźć rozwiązanie bez skutków ubocznych, albo użyć tradycyjnej pętli for.
źródło