Variabel yang digunakan dalam ekspresi lambda harus final atau efektif final

134

Variabel yang digunakan dalam ekspresi lambda harus final atau efektif final

Ketika saya mencoba menggunakannya calTzmenunjukkan kesalahan ini.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            VTimeZone v = (VTimeZone) component;
            v.getTimeZoneId();
            if (calTz == null) {
                calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}
pengguna3610470
sumber
5
Anda tidak dapat memodifikasi calTzdari lambda.
Elliott Frisch
2
Saya berasumsi ini adalah salah satu dari hal-hal yang tidak dilakukan pada waktunya untuk Java 8. Tetapi Java 8 adalah 2014. Scala dan Kotlin telah mengizinkan ini selama bertahun-tahun, jadi itu jelas mungkin. Apakah Jawa pernah berencana untuk menghilangkan pembatasan aneh ini?
GlenPeterson
5
Berikut ini tautan yang diperbarui ke komentar @MSDousti.
geisterfurz007
Saya pikir Anda bisa menggunakan Completable Futures sebagai solusinya.
Kraulain
Satu hal penting yang saya amati - Anda dapat menggunakan variabel statis alih-alih variabel normal (Ini membuatnya efektif akhir kurasa)
kaushalpranav

Jawaban:

68

Sebuah finalcara variabel yang dapat dipakai hanya satu kali. di Java Anda tidak bisa menggunakan variabel non-final di lambda juga di kelas dalam anonim.

Anda dapat memperbaiki kode Anda dengan yang lama untuk setiap loop:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    try {
        for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(calTz==null) {
               calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
           }
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Bahkan jika saya tidak mengerti beberapa bagian dari kode ini:

  • Anda memanggil v.getTimeZoneId();tanpa menggunakan nilai baliknya
  • dengan tugas calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());Anda tidak mengubah yang semula disahkan calTzdan Anda tidak menggunakannya dalam metode ini
  • Anda selalu kembali null, mengapa Anda tidak menetapkan voidsebagai tipe pengembalian?

Semoga juga tips ini membantu Anda untuk meningkatkan.

Francesco Pitzalis
sumber
kita dapat menggunakan variabel statis non final
Narendra Jaggi
93

Meskipun jawaban lain membuktikan persyaratan, mereka tidak menjelaskan mengapa persyaratan itu ada.

JLS menyebutkan mengapa dalam §15.27.2 :

Pembatasan untuk variabel akhir yang efektif melarang akses ke variabel lokal yang berubah secara dinamis, yang tangkapannya kemungkinan akan menimbulkan masalah konkurensi.

Untuk menurunkan risiko bug, mereka memutuskan untuk memastikan variabel yang ditangkap tidak pernah bermutasi.

Dioksin
sumber
11
Jawaban yang bagus +1, dan saya terkejut dengan betapa sedikitnya liputan alasan untuk mendapatkan hasil akhir yang efektif. Catatan: Variabel lokal hanya dapat ditangkap oleh lambda jika variabel tersebut juga ditentukan sebelum badan lambda. Kedua persyaratan tersebut tampaknya memastikan bahwa mengakses variabel lokal akan aman.
Tim Biegeleisen
2
ada ide mengapa ini dibatasi hanya untuk variabel lokal, dan bukan anggota kelas? Saya menemukan diri saya sering menghindari masalah dengan menyatakan variabel saya sebagai anggota kelas ...
David Refaeli
4
@DavidRefaeli Anggota kelas dilindungi / terpengaruh oleh model memori, yang jika diikuti, akan menghasilkan hasil yang dapat diprediksi saat dibagikan. Variabel lokal tidak, seperti yang disebutkan dalam §17.4.1
Dioxin
Ini adalah hack konyol, yang harus dihapus. Compiler harus memperingatkan tentang kemungkinan akses variabel cross-thread, tetapi harus mengizinkannya. Atau, harus cukup pintar untuk mengetahui apakah lambda Anda berjalan di utas yang sama, atau berjalan paralel, dll. Ini adalah batasan konyol, yang membuat saya sedih. Dan seperti yang telah disebutkan orang lain, masalah tidak ada dalam misalnya C #.
Josh M.
@ Josh. C # juga memungkinkan Anda untuk membuat tipe nilai yang bisa berubah , yang orang sarankan untuk menghindari masalah. Alih-alih memiliki prinsip seperti itu, Java memutuskan untuk mencegahnya sepenuhnya. Itu mengurangi kesalahan pengguna, dengan harga fleksibilitas. Saya tidak setuju dengan pembatasan ini, tapi itu bisa dibenarkan. Akuntansi untuk paralelisme akan membutuhkan beberapa pekerjaan tambahan pada akhir kompiler, yang mungkin mengapa rute " peringatkan akses lintas-threaded " tidak diambil. Pengembang yang mengerjakan spesifikasi mungkin akan menjadi satu-satunya konfirmasi kami untuk ini.
Dioxin
58

Dari lambda, Anda tidak bisa mendapatkan referensi ke apa pun yang belum final. Anda harus mendeklarasikan pembungkus akhir dari luar lamda untuk menyimpan variabel Anda.

Saya telah menambahkan objek 'referensi' terakhir sebagai pembungkus ini.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    final AtomicReference<TimeZone> reference = new AtomicReference<>();

    try {
       cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(reference.get()==null) {
               reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
           }
           });
    } catch (Exception e) {
        //log.warn("Unable to determine ical timezone", e);
    }
    return reference.get();
}   
DMozzy
sumber
Saya sedang memikirkan pendekatan yang sama atau serupa - tetapi saya ingin melihat beberapa saran ahli / umpan balik tentang jawaban ini?
YoYo
4
Kode ini melewatkan inisial reference.set(calTz);atau referensi harus dibuat menggunakan new AtomicReference<>(calTz), jika tidak TimeZone non-nol yang disediakan sebagai parameter akan hilang.
Julien Kronegg
8
Ini harus menjadi jawaban pertama. AtomicReference (atau kelas Atomic___ serupa) bekerja di sekitar batasan ini dengan aman di setiap keadaan yang memungkinkan.
GlenPeterson
1
Setuju, ini harus menjadi jawaban yang diterima. Jawaban yang lain memberikan informasi yang berguna tentang cara kembali ke model pemrograman yang tidak berfungsi, dan mengapa hal ini dilakukan, tetapi tidak benar-benar memberi tahu Anda cara mengatasi masalah!
Jonathan Benn
2
@GlenPeterson dan merupakan keputusan yang mengerikan juga, tidak hanya itu jauh lebih lambat dengan cara ini, tetapi Anda juga mengabaikan properti efek samping yang diamanatkan oleh dokumentasi.
Eugene
41

Java 8 memiliki konsep baru yang disebut variabel "Final efektif". Ini berarti bahwa variabel lokal non-final yang nilainya tidak pernah berubah setelah inisialisasi disebut "Efektif Final".

Konsep ini diperkenalkan karena sebelum Java 8 , kami tidak dapat menggunakan variabel lokal non-final dalam kelas anonim . Jika Anda ingin memiliki akses ke variabel lokal di kelas anonim , Anda harus membuatnya final.

Ketika lambda diperkenalkan, pembatasan ini dikurangi. Oleh karena itu kebutuhan untuk membuat variabel lokal final jika tidak diubah setelah diinisialisasi sebagai lambda itu sendiri tidak lain adalah kelas anonim.

Java 8 menyadari sulitnya mendeklarasikan variabel lokal final setiap kali pengembang menggunakan lambda, memperkenalkan konsep ini, dan membuatnya tidak perlu untuk membuat variabel lokal final. Jadi jika Anda melihat aturan untuk kelas anonim tidak berubah, hanya saja Anda tidak perlu menulis finalkata kunci setiap kali menggunakan lambdas.

Saya menemukan penjelasan yang bagus di sini

Dinesh Arora
sumber
Pemformatan kode hanya boleh digunakan untuk kode , bukan untuk istilah teknis secara umum. effectively finalbukan kode, itu terminologi. Lihat Kapan pemformatan kode harus digunakan untuk teks bukan kode? pada Meta Stack Overflow .
Charles Duffy
(Jadi " finalkata kunci" adalah kata kode dan benar untuk memformat seperti itu, tetapi ketika Anda menggunakan "final" secara deskriptif daripada sebagai kode, itu terminologi sebagai gantinya).
Charles Duffy
9

Dalam contoh Anda, Anda dapat mengganti forEachlamdba dengan forloop sederhana dan memodifikasi variabel apa pun secara bebas. Atau, mungkin, perbaiki kode Anda sehingga Anda tidak perlu memodifikasi variabel apa pun. Namun, saya akan menjelaskan secara lengkap apa arti kesalahan itu dan bagaimana cara mengatasinya.

Spesifikasi Bahasa Java 8, §15.27.2 :

Setiap variabel lokal, parameter formal, atau parameter pengecualian yang digunakan tetapi tidak dideklarasikan dalam ekspresi lambda harus dinyatakan final atau secara efektif final ( §4.12.4 ), atau kesalahan waktu kompilasi terjadi ketika penggunaan dicoba.

Pada dasarnya Anda tidak dapat mengubah variabel lokal ( calTzdalam hal ini) dari dalam lambda (atau kelas lokal / anonim). Untuk mencapainya di Java, Anda harus menggunakan objek yang bisa berubah dan memodifikasinya (melalui variabel terakhir) dari lambda. Salah satu contoh objek yang bisa berubah di sini adalah array dari satu elemen:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    TimeZone[] result = { null };
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            ...
            result[0] = ...;
            ...
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return result[0];
}
Alexander Udalov
sumber
Cara lain adalah dengan menggunakan bidang objek. Misalnya, hasil MyObj = MyObj baru (); ...; result.timeZone = ...; ....; mengembalikan result.timezone; Perhatikan bahwa, seperti yang dijelaskan di atas, ini membuat Anda terpapar pada masalah keamanan benang. Lihat stackoverflow.com/a/50341404/7092558
Gibezynu Nu
0

jika tidak perlu memodifikasi variabel daripada solusi umum untuk masalah seperti ini adalah mengekstraksi bagian kode yang menggunakan lambda dan menggunakan kata kunci terakhir pada metode-parameter.

robie2011
sumber
0

Variabel yang digunakan dalam ekspresi lambda harus final atau efektif final, tetapi Anda bisa menetapkan nilai ke array elemen final.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        TimeZone calTzLocal[] = new TimeZone[1];
        calTzLocal[0] = calTz;
        cal.getComponents().get("VTIMEZONE").forEach(component -> {
            TimeZone v = component;
            v.getTimeZoneId();
            if (calTzLocal[0] == null) {
                calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}
Andreas Foteas
sumber
Ini sangat mirip dengan saran dari Alexander Udalov. Selain itu saya pikir pendekatan ini mengandalkan efek samping.
Scratte