Memodifikasi variabel lokal forEach
memberikan kesalahan kompilasi:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
Dengan Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Ada ide bagaimana mengatasi ini?
Jawaban:
Gunakan pembungkus
Semua jenis pembungkus itu bagus.
Dengan Java 8+ , gunakan salah satu
AtomicInteger
:... atau array:
Dengan Java 10+ :
Catatan: berhati-hatilah jika Anda menggunakan aliran paralel. Anda mungkin tidak mendapatkan hasil yang diharapkan. Solusi lain seperti Stuart mungkin lebih disesuaikan untuk kasus tersebut.
Untuk tipe selain
int
Tentu saja, ini masih berlaku untuk tipe selain
int
. Anda hanya perlu mengubah jenis pembungkusanAtomicReference
menjadi larik atau jenis itu. Misalnya, jika Anda menggunakan aString
, cukup lakukan hal berikut:Gunakan array:
Atau dengan Java 10+:
sumber
int[] ordinal = { 0 };
? Bisakah Anda menjelaskannya. Terima kasihclass MyClass$1 { int value; }
Di kelas biasa, ini berarti Anda membuat kelas dengan variabel paket-pribadi bernamavalue
. Ini sama, tetapi kelas tersebut sebenarnya anonim bagi kami, bukan bagi kompiler atau JVM. Java akan mengkompilasi kode tersebut seolah-olah ituMyClass$1 wrapper = new MyClass$1();
. Dan kelas anonim menjadi kelas lain. Pada akhirnya, kami hanya menambahkan gula sintaksis di atasnya agar dapat dibaca. Juga, kelas tersebut adalah kelas dalam dengan bidang paket-pribadi. Bidang itu bisa digunakan.Ini cukup dekat dengan masalah XY . Artinya, pertanyaan yang diajukan pada dasarnya adalah bagaimana memutasi variabel lokal yang ditangkap dari lambda. Tetapi tugas sebenarnya yang ada adalah bagaimana memberi nomor pada elemen daftar.
Dalam pengalaman saya, lebih dari 80% dari waktu ada pertanyaan tentang bagaimana mengubah lokal yang ditangkap dari dalam lambda, ada cara yang lebih baik untuk melanjutkan. Biasanya ini melibatkan pengurangan, tetapi dalam kasus ini teknik menjalankan aliran melalui indeks daftar berlaku dengan baik:
sumber
list
adalahRandomAccess
daftarJika Anda hanya perlu meneruskan nilai dari luar ke lambda, dan tidak mengeluarkannya, Anda dapat melakukannya dengan kelas anonim biasa alih-alih lambda:
sumber
Karena variabel yang digunakan dari luar lamda harus (secara implisit) final, Anda harus menggunakan sesuatu seperti
AtomicInteger
atau menulis struktur data Anda sendiri.Lihat https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
sumber
Jika Anda menggunakan Java 10, Anda dapat menggunakannya
var
untuk itu:sumber
Alternatif untuk
AtomicInteger
(atau objek lain yang dapat menyimpan nilai) adalah dengan menggunakan array:Tapi lihat jawaban Stuart : mungkin ada cara yang lebih baik untuk menangani kasus Anda.
sumber
Saya tahu itu pertanyaan lama, tetapi jika Anda merasa nyaman dengan solusinya, mengingat fakta bahwa variabel eksternal harus final, Anda cukup melakukan ini:
Mungkin bukan yang paling elegan, atau bahkan yang paling benar, tetapi itu akan berhasil.
sumber
Anda dapat membungkusnya untuk mengatasi kompiler tetapi harap diingat bahwa efek samping di lambda tidak disarankan.
Mengutip javadoc
sumber
Saya punya masalah yang sedikit berbeda. Alih-alih menambahkan variabel lokal di forEach, saya perlu menetapkan objek ke variabel lokal.
Saya menyelesaikan ini dengan mendefinisikan kelas domain dalam pribadi yang membungkus kedua daftar yang ingin saya ulangi (countryList) dan keluaran yang saya harapkan dari daftar itu (foundCountry). Kemudian menggunakan Java 8 "forEach", saya mengulangi bidang daftar, dan ketika objek yang saya inginkan ditemukan, saya menetapkan objek itu ke bidang keluaran. Jadi ini memberikan nilai ke bidang variabel lokal, tidak mengubah variabel lokal itu sendiri. Saya percaya bahwa karena variabel lokal itu sendiri tidak berubah, kompilator tidak mengeluh. Saya kemudian dapat menggunakan nilai yang saya tangkap di bidang keluaran, di luar daftar.
Objek Domain:
Objek pembungkus:
Operasi berulang:
Anda dapat menghapus metode kelas pembungkus "setCountryList ()" dan membuat kolom "countryList" final, tetapi saya tidak mendapatkan kesalahan kompilasi yang membiarkan detail ini sebagaimana adanya.
sumber
Untuk mendapatkan solusi yang lebih umum, Anda dapat menulis kelas Wrapper generik:
(ini adalah varian dari solusi yang diberikan oleh Almir Campos).
Dalam kasus khusus ini bukan solusi yang baik, karena
Integer
lebih buruk daripadaint
tujuan Anda, bagaimanapun solusi ini lebih umum menurut saya.sumber
Ya, Anda dapat memodifikasi variabel lokal dari dalam lambda (dengan cara yang ditunjukkan oleh jawaban lain), tetapi Anda tidak boleh melakukannya. Lambda telah digunakan untuk gaya pemrograman fungsional dan ini berarti: Tidak ada efek samping. Apa yang ingin Anda lakukan dianggap gaya yang buruk. Ini juga berbahaya jika terjadi aliran paralel.
Anda harus menemukan solusi tanpa efek samping atau menggunakan for loop tradisional.
sumber