Das Ändern einer lokalen Variablen in forEach
führt zu einem Kompilierungsfehler:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
Mit Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Irgendeine Idee, wie man das löst?
Antworten:
Verwenden Sie eine Hülle
Jede Art von Verpackung ist gut.
Mit Java 8+ , verwenden entweder ein
AtomicInteger
:... oder ein Array:
Mit Java 10+ :
Hinweis: Seien Sie sehr vorsichtig, wenn Sie einen parallelen Stream verwenden. Möglicherweise erhalten Sie nicht das erwartete Ergebnis. Andere Lösungen wie die von Stuart sind möglicherweise besser für diese Fälle geeignet.
Für andere Typen als
int
Dies gilt natürlich weiterhin für andere Typen als
int
. Sie müssen nur den Umhüllungstyp in einAtomicReference
oder ein Array dieses Typs ändern . Wenn Sie beispielsweise a verwendenString
, gehen Sie wie folgt vor:Verwenden Sie ein Array:
Oder mit Java 10+:
quelle
int[] ordinal = { 0 };
? Kannst du es erklären. Vielen Dankclass MyClass$1 { int value; }
In einer normalen Klasse bedeutet dies, dass Sie eine Klasse mit einer paketprivaten Variablen namens erstellenvalue
. Dies ist das gleiche, aber die Klasse ist für uns tatsächlich anonym, nicht für den Compiler oder die JVM. Java kompiliert den Code so, als ob er es wäreMyClass$1 wrapper = new MyClass$1();
. Und die anonyme Klasse wird nur eine weitere Klasse. Am Ende haben wir nur syntaktischen Zucker hinzugefügt, um ihn lesbar zu machen. Außerdem ist die Klasse eine innere Klasse mit einem paketprivaten Feld. Dieses Feld ist verwendbar.Dies kommt einem XY-Problem ziemlich nahe . Das heißt, die Frage, die gestellt wird, ist im Wesentlichen, wie eine erfasste lokale Variable von einem Lambda mutiert werden kann. Die eigentliche Aufgabe besteht jedoch darin, die Elemente einer Liste zu nummerieren.
Nach meiner Erfahrung gibt es in mehr als 80% der Fälle die Frage, wie ein gefangener Einheimischer aus einem Lambda heraus mutiert werden kann. Es gibt einen besseren Weg, um fortzufahren. Normalerweise beinhaltet dies eine Reduzierung, aber in diesem Fall gilt die Technik, einen Stream über die Listenindizes auszuführen, gut:
quelle
list
es eineRandomAccess
Liste gibtWenn Sie den Wert nur von außen an das Lambda übergeben müssen und ihn nicht herausholen müssen, können Sie dies mit einer regulären anonymen Klasse anstelle eines Lambda tun:
quelle
Da die verwendeten Variablen von außerhalb der Lamda (implizit) endgültig sein müssen, müssen Sie so etwas wie verwenden
AtomicInteger
oder Ihre eigene Datenstruktur schreiben.Siehe https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
quelle
Wenn Sie mit Java 10 arbeiten, können Sie Folgendes verwenden
var
:quelle
Eine Alternative zu
AtomicInteger
(oder einem anderen Objekt, das einen Wert speichern kann) ist die Verwendung eines Arrays:Aber siehe die Antwort von Stuart : Es könnte einen besseren Weg geben, um mit Ihrem Fall umzugehen.
quelle
Ich weiß, dass dies eine alte Frage ist, aber wenn Sie mit einer Problemumgehung vertraut sind und die externe Variable endgültig sein sollte, können Sie dies einfach tun:
Vielleicht nicht das eleganteste oder sogar das korrekteste, aber es wird reichen.
quelle
Sie können es einpacken, um den Compiler zu umgehen. Beachten Sie jedoch, dass von Nebenwirkungen bei Lambdas abgeraten wird.
Um den Javadoc zu zitieren
quelle
Ich hatte ein etwas anderes Problem. Anstatt eine lokale Variable in forEach zu erhöhen, musste ich der lokalen Variablen ein Objekt zuweisen.
Ich habe dieses Problem gelöst, indem ich eine private innere Domänenklasse definiert habe, die sowohl die Liste, über die ich iterieren möchte (countryList), als auch die Ausgabe, die ich von dieser Liste erhalten möchte (foundCountry), umschließt. Dann iteriere ich mit Java 8 "forEach" über das Listenfeld, und wenn das gewünschte Objekt gefunden wird, ordne ich dieses Objekt dem Ausgabefeld zu. Dadurch wird einem Feld der lokalen Variablen ein Wert zugewiesen, ohne dass die lokale Variable selbst geändert wird. Ich glaube, da sich die lokale Variable selbst nicht ändert, beschwert sich der Compiler nicht. Ich kann dann den Wert verwenden, den ich im Ausgabefeld außerhalb der Liste erfasst habe.
Domänenobjekt:
Wrapper-Objekt:
Iterierte Operation:
Sie könnten die Wrapper-Klassenmethode "setCountryList ()" entfernen und das Feld "countryList" endgültig machen, aber ich habe keine Kompilierungsfehler erhalten, wenn diese Details unverändert bleiben.
quelle
Um eine allgemeinere Lösung zu erhalten, können Sie eine generische Wrapper-Klasse schreiben:
(Dies ist eine Variante der von Almir Campos gegebenen Lösung).
Im konkreten Fall ist dies keine gute Lösung, da sie
Integer
schlechter ist alsint
für Ihren Zweck. Trotzdem denke ich, dass diese Lösung allgemeiner ist.quelle
Ja, Sie können lokale Variablen in Lambdas ändern (wie in den anderen Antworten gezeigt), aber Sie sollten dies nicht tun. Lambdas sind für den funktionalen Programmierstil gedacht und das bedeutet: Keine Nebenwirkungen. Was Sie tun möchten, wird als schlechter Stil angesehen. Es ist auch gefährlich bei parallelen Streams.
Sie sollten entweder eine Lösung ohne Nebenwirkungen finden oder eine herkömmliche for-Schleife verwenden.
quelle