Saya memiliki kelas dengan private static final
bidang yang, sayangnya, saya perlu mengubahnya saat dijalankan.
Menggunakan refleksi saya mendapatkan kesalahan ini: java.lang.IllegalAccessException: Can not set static final boolean field
Apakah ada cara untuk mengubah nilainya?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
System.out/in/err
begitu "istimewa" sehingga Java Memory Model harus menyebutkannya secara khusus. Mereka bukan contoh yang harus diikuti.Jawaban:
Dengan asumsi tidak ada
SecurityManager
yang mencegah Anda melakukan ini, Anda dapat menggunakansetAccessible
untuk menyiasatiprivate
dan mengatur ulang pengubah untuk menyingkirkanfinal
, dan benar-benar memodifikasiprivate static final
bidang.Ini sebuah contoh:
Dengan asumsi tidak
SecurityException
yang dilemparkan, kode di atas dicetak"Everything is true"
.Apa yang sebenarnya dilakukan di sini adalah sebagai berikut:
boolean
nilai primitiftrue
danfalse
dalammain
diautobox ke tipe referensiBoolean
"konstanta"Boolean.TRUE
danBoolean.FALSE
public static final Boolean.FALSE
agar merujuk ke yangBoolean
dimaksud olehBoolean.TRUE
false
autoboxed menjadiBoolean.FALSE
, itu merujuk samaBoolean
dengan yang dirujuk olehBoolean.TRUE
"false"
sekarang adalah"true"
Pertanyaan-pertanyaan Terkait
static final File.separatorChar
pengujian unitInteger
cache, bermutasiString
, dllPeringatan
Perhatian yang luar biasa harus diambil setiap kali Anda melakukan sesuatu seperti ini. Mungkin tidak berfungsi karena
SecurityManager
ada, tetapi meskipun tidak, tergantung pada pola penggunaan, mungkin ada atau tidak bisa.Lihat juga
private static final boolean
, karena itu tidak dapat digariskan sebagai konstanta waktu kompilasi dan dengan demikian nilai "baru" mungkin tidak dapat diamatiLampiran: Tentang manipulasi bitwise
Pada dasarnya,
mematikan bit yang sesuai dengan
Modifier.FINAL
darifield.getModifiers()
.&
adalah bitwise-and, and~
merupakan bitwise-komplemen.Lihat juga
Ingat Ekspresi Konstan
Masih belum bisa menyelesaikan ini ?, sudah jatuh dalam depresi seperti yang saya lakukan untuk itu? Apakah kode Anda terlihat seperti ini?
Membaca komentar pada jawaban ini, khususnya yang oleh @Pemo, mengingatkan saya bahwa Konstan Ekspresi ditangani berbeda sehingga tidak mungkin untuk memodifikasinya. Karenanya Anda perlu mengubah kode Anda agar terlihat seperti ini:
jika Anda bukan pemilik kelas ... Saya merasa Anda!
Untuk lebih jelasnya mengapa perilaku ini membaca ini ?
sumber
getDeclaredField()
bukangetField()
untuk kelas targetfinal String myConstant = "x";
dan akan gagal: ingat bahwa konstanta waktu kompilasi akan digariskan oleh kompiler sehingga ketika Anda akan menulis kode sepertiSystem.out.println(myConstant);
itu akan dikompilasiSystem.out.println("x");
karena kompiler mengetahui nilai konstanta pada waktu kompilasi. Untuk menghilangkan masalah ini, Anda perlu menginisialisasi konstanta saat runtime likefinal String myConstant = new String("x");
. Juga dalam kasus primitif sepertifinal int myField = 11
penggunaanfinal int myField = new Integer(11);
ataufinal Integer myField = 11;
Jika nilai yang diberikan ke
static final boolean
bidang diketahui pada waktu kompilasi, itu adalah konstan. Bidang primitif atauString
tipe dapat berupa konstanta waktu kompilasi. Konstanta akan digarisbawahi dalam kode apa pun yang merujuk bidang. Karena bidang tersebut tidak benar-benar dibaca saat runtime, mengubahnya maka tidak akan berpengaruh.The Java spesifikasi bahasa mengatakan ini:
Ini sebuah contoh:
Jika Anda mendekompilasi
Checker
, Anda akan melihat bahwa alih-alih referensiFlag.FLAG
, kode hanya mendorong nilai 1 (true
) ke tumpukan (instruksi # 3).sumber
public static final Boolean FALSE = new Boolean(false)
bukanpublic static final boolean FALSE = false
Sedikit keingintahuan dari Spesifikasi Bahasa Jawa, bab 17, bagian 17.5.4 "Bidang yang Dilindungi Tulis":
Sumber: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
sumber
Saya juga mengintegrasikannya dengan joor library
Gunakan saja
Saya juga memperbaiki masalah
override
yang sepertinya terlewatkan oleh solusi sebelumnya. Namun gunakan ini dengan sangat hati-hati, hanya ketika tidak ada solusi bagus lainnya.sumber
Bersamaan dengan jawaban berperingkat teratas Anda dapat menggunakan pendekatan yang paling sederhana.
FieldUtils
Kelas Apache commons sudah memiliki metode khusus yang dapat melakukan hal-hal tersebut. Tolong, lihatFieldUtils.removeFinalModifier
metode. Anda harus menentukan instance bidang target dan flag pemaksaan aksesibilitas (jika Anda bermain dengan bidang non-publik). Info lebih lanjut dapat Anda temukan di sini .sumber
java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
Jika ada Manajer Keamanan, seseorang dapat menggunakan
AccessController.doPrivileged
Mengambil contoh yang sama dari jawaban yang diterima di atas:
Dalam ekspresi lambda
AccessController.doPrivileged
,, dapat disederhanakan untuk:sumber
Jawaban yang diterima bekerja untuk saya sampai digunakan pada JDK 1.8u91. Kemudian saya menyadari itu gagal di
field.set(null, newValue);
garis ketika saya telah membaca nilai melalui refleksi sebelum meneleponsetFinalStatic
metode.Mungkin pembacaan tersebut menyebabkan pengaturan yang berbeda dari internal Java refleksi (yaitu
sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
dalam kasus gagal, bukansun.reflect.UnsafeStaticObjectFieldAccessorImpl
dalam kasus sukses) tapi saya tidak menguraikannya lebih jauh.Karena saya perlu menetapkan nilai baru untuk sementara berdasarkan nilai lama dan kemudian mengembalikan nilai lama, saya mengubah sedikit tanda tangan untuk menyediakan fungsi komputasi secara eksternal dan juga mengembalikan nilai lama:
Namun untuk kasus umum ini tidak akan cukup.
sumber
Sekalipun
final
sebuah field dapat dimodifikasi di luar initializer statis dan (setidaknya JVM HotSpot) akan mengeksekusi bytecode dengan sangat baik.Masalahnya adalah kompiler Java tidak mengizinkan ini, tetapi ini dapat dengan mudah dilewati menggunakan
objectweb.asm
. Berikut ini adalah classfile yang valid sempurna yang melewati verifikasi bytecode dan berhasil dimuat dan diinisialisasi di bawah JVM HotSpot OpenJDK12:Di Jawa, kelas terlihat berbicara kasar sebagai berikut:
yang tidak dapat dikompilasi dengan
javac
, tetapi dapat dimuat dan dieksekusi oleh JVM.JVM HotSpot memiliki perlakuan khusus terhadap kelas-kelas semacam itu dalam arti bahwa ia mencegah "konstanta" seperti itu untuk berpartisipasi dalam pelipatan konstan. Pemeriksaan ini dilakukan pada fase penulisan ulang bytecode inisialisasi kelas :
Satu-satunya batasan yang diperiksa oleh JVM HotSpot adalah bahwa
final
bidang tidak boleh dimodifikasi di luar kelas tempatfinal
bidang tersebut dideklarasikan.sumber
Hanya melihat pertanyaan itu di salah satu pertanyaan wawancara, jika mungkin untuk mengubah variabel akhir dengan refleksi atau runtime. Sangat tertarik, sehingga saya menjadi seperti ini:
Beberapa kelas sederhana dengan variabel String akhir. Jadi di import java.lang.reflect.Field kelas utama;
Outputnya adalah sebagai berikut:
Menurut dokumentasi https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
sumber
static
bidang terakhir, jadi kode ini tidak berfungsi.setAccessible(true)
hanya berfungsi untuk menyetel bidang instance akhir.Jika bidang Anda hanya bersifat pribadi, Anda dapat melakukan ini:
dan melempar / menangani NoSuchFieldException
sumber
Inti dari a
final
bidang adalah bahwa itu tidak dapat dipindahkan setelah ditetapkan. JVM menggunakan jaminan ini untuk menjaga konsistensi di berbagai tempat (misalnya kelas dalam yang merujuk variabel luar). Jadi tidak. Mampu melakukannya akan mematahkan JVM!Solusinya adalah tidak mendeklarasikannya
final
sejak awal.sumber
final
memiliki peran khusus dalam eksekusi multithreaded - mengubahfinal
nilai akan merusak model memori Java juga.final
tidak boleh dinyatakanstatic
.