Ganti bidang final statis pribadi menggunakan refleksi Java

479

Saya memiliki kelas dengan private static finalbidang 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);
fixitagain
sumber
4
Ide yang buruk. Saya akan mencoba untuk mendapatkan sumber dan mengkompilasi ulang (atau bahkan mendekompilasi / mengkompilasi ulang) sebagai gantinya.
Bill K
System.out adalah bidang final statis publik, tetapi dapat diubah juga.
diperbaiki
19
@irreputable System.out/in/errbegitu "istimewa" sehingga Java Memory Model harus menyebutkannya secara khusus. Mereka bukan contoh yang harus diikuti.
Tom Hawtin - tackline
8
baik poin saya adalah untuk menemukan hack di antara untuk memiliki aplikasi saya bekerja sampai lib yang bertanggung jawab membuat perubahan pada rilis berikutnya jadi saya tidak perlu hack lagi ...
fixitagain
1
@ Bill K dari sepuluh tahun yang lalu: Akan sangat bagus untuk mengkompilasi ulang tetapi ada pada sistem yang digunakan dan saya hanya perlu menambalnya sampai kita dapat memperbarui aplikasi yang digunakan!
Bill K

Jawaban:

888

Dengan asumsi tidak ada SecurityManageryang mencegah Anda melakukan ini, Anda dapat menggunakan setAccessibleuntuk menyiasati privatedan mengatur ulang pengubah untuk menyingkirkan final, dan benar-benar memodifikasiprivate static final bidang.

Ini sebuah contoh:

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Dengan asumsi tidak SecurityException yang dilemparkan, kode di atas dicetak "Everything is true".

Apa yang sebenarnya dilakukan di sini adalah sebagai berikut:

  • Nilai- booleannilai primitif truedan falsedalam maindiautobox ke tipe referensiBoolean "konstanta" Boolean.TRUEdanBoolean.FALSE
  • Refleksi digunakan untuk mengubah public static final Boolean.FALSE agar merujuk ke yang Booleandimaksud olehBoolean.TRUE
  • Akibatnya, selanjutnya setiap kali falseautoboxed menjadiBoolean.FALSE , itu merujuk sama Booleandengan yang dirujuk olehBoolean.TRUE
  • Semua yang ada "false"sekarang adalah"true"

Pertanyaan-pertanyaan Terkait


Peringatan

Perhatian yang luar biasa harus diambil setiap kali Anda melakukan sesuatu seperti ini. Mungkin tidak berfungsi karena SecurityManagerada, tetapi meskipun tidak, tergantung pada pola penggunaan, mungkin ada atau tidak bisa.

JLS 17.5.3 Modifikasi Selanjutnya Bidang Final

Dalam beberapa kasus, seperti deserialization, sistem perlu mengubah finalbidang objek setelah konstruksi. finalbidang dapat diubah melalui refleksi dan sarana tergantung implementasi lainnya. Satu-satunya pola di mana ini memiliki semantik yang masuk akal adalah pola di mana objek dibangun dan kemudian finalbidang objek diperbarui. Objek tidak boleh dibuat terlihat oleh utas lain, atau finalbidang tidak boleh dibaca, sampai semua pembaruan ke finalbidang objek selesai. Pembekuan finalbidang terjadi baik pada akhir konstruktor di manafinal bidang diatur, dan segera setelah setiap modifikasi finalbidang melalui refleksi atau mekanisme khusus lainnya.

Meski begitu, ada sejumlah komplikasi. Jika finalbidang diinisialisasi ke konstanta waktu kompilasi dalam deklarasi bidang, perubahan pada finalbidang tersebut mungkin tidak diamati, karena penggunaan itufinal bidang tersebut diganti pada waktu kompilasi dengan konstanta waktu kompilasi.

Masalah lain adalah bahwa spesifikasi memungkinkan optimalisasi finalbidang secara agresif . Dalam utas, diizinkan untuk menyusun ulang bacaan finalbidang dengan modifikasi bidang final yang tidak terjadi di konstruktor.

Lihat juga

  • JLS 15.28 Ekspresi Konstan
    • Tidak mungkin bahwa teknik ini bekerja dengan primitif private static final boolean, karena itu tidak dapat digariskan sebagai konstanta waktu kompilasi dan dengan demikian nilai "baru" mungkin tidak dapat diamati

Lampiran: Tentang manipulasi bitwise

Pada dasarnya,

field.getModifiers() & ~Modifier.FINAL

mematikan bit yang sesuai dengan Modifier.FINALdari field.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?

public class A {
    private final String myVar = "Some Value";
}

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:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

jika Anda bukan pemilik kelas ... Saya merasa Anda!

Untuk lebih jelasnya mengapa perilaku ini membaca ini ?

polygenelubricants
sumber
41
@thecoop, @HalfBrian: tidak ada keraguan bahwa ini sangat jahat , tetapi contoh ini dipilih oleh desain. Jawaban saya hanya menunjukkan bagaimana, dalam beberapa keadaan, ini mungkin. Contoh paling menjijikkan yang bisa saya pikirkan adalah sengaja dipilih dengan harapan bahwa mungkin orang akan langsung jijik dengan daripada jatuh cinta pada teknik.
polygenelubricants
59
Hai kawan. Saya mendengar Anda menyukai refleksi, jadi saya merefleksikannya di lapangan sehingga Anda dapat berefleksi saat Anda berefleksi.
Matthew Flaschen
11
Perhatikan bahwa Boolean.FALSE tidak pribadi. Apakah ini benar-benar berfungsi dengan anggota "private final static"?
mgaert
15
@ Madert tidak, tetapi Anda harus menggunakan getDeclaredField()bukan getField()untuk kelas target
eis
11
+1. Bagi mereka yang akan mencoba mengubah sesuatu seperti final String myConstant = "x";dan akan gagal: ingat bahwa konstanta waktu kompilasi akan digariskan oleh kompiler sehingga ketika Anda akan menulis kode seperti System.out.println(myConstant);itu akan dikompilasi System.out.println("x");karena kompiler mengetahui nilai konstanta pada waktu kompilasi. Untuk menghilangkan masalah ini, Anda perlu menginisialisasi konstanta saat runtime like final String myConstant = new String("x");. Juga dalam kasus primitif seperti final int myField = 11penggunaan final int myField = new Integer(11);ataufinal Integer myField = 11;
Pshemo
58

Jika nilai yang diberikan ke static final booleanbidang diketahui pada waktu kompilasi, itu adalah konstan. Bidang primitif atau String 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:

Jika bidang adalah variabel konstan (§4.12.4), maka menghapus kata kunci akhir atau mengubah nilainya tidak akan merusak kompatibilitas dengan binari yang sudah ada sebelumnya dengan menyebabkan mereka tidak berjalan, tetapi mereka tidak akan melihat nilai baru untuk penggunaan tersebut. dari lapangan kecuali jika mereka dikompilasi ulang. Ini benar bahkan jika penggunaannya sendiri bukan ekspresi konstanta waktu kompilasi (§15.28)

Ini sebuah contoh:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

Jika Anda mendekompilasi Checker, Anda akan melihat bahwa alih-alih referensi Flag.FLAG, kode hanya mendorong nilai 1 ( true) ke tumpukan (instruksi # 3).

0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3:   iconst_1
4:   invokevirtual   #3; //Method java/io/PrintStream.println:(Z)V
7:   return
erickson
sumber
Itu adalah pemikiran pertama saya, tetapi kemudian saya ingat Java dikompilasi pada saat runtime, jika Anda mengatur ulang bit, itu hanya akan dikompilasi ulang dengan itu sebagai variabel, bukan konstanta.
Bill K
4
@ Bill K - Tidak, ini tidak merujuk pada kompilasi JIT. File-file kelas dependen akan benar-benar berisi nilai-nilai sebaris, dan tidak ada referensi ke kelas independen. Ini adalah eksperimen yang cukup sederhana untuk diuji; Saya akan menambahkan contoh.
erickson
1
Bagaimana jive ini dengan jawaban @polygenelubricants di mana ia mendefinisikan ulang Boolean.false? - tetapi Anda benar, saya telah melihat perilaku ini ketika ada hal-hal yang tidak dikompilasi ulang dengan benar.
Bill K
26
@ Bill K - dalam jawaban polygenlubricants ', bidangnya bukan konstanta waktu kompilasi. Ini public static final Boolean FALSE = new Boolean(false)bukanpublic static final boolean FALSE = false
erickson
17

Sedikit keingintahuan dari Spesifikasi Bahasa Jawa, bab 17, bagian 17.5.4 "Bidang yang Dilindungi Tulis":

Biasanya, bidang yang final dan statis tidak dapat dimodifikasi. Namun, System.in, System.out, dan System.err adalah bidang final statis yang, karena alasan sebelumnya, harus diizinkan untuk diubah dengan metode System.setIn, System.setOut, dan System.setErr. Kami menyebut bidang-bidang ini sebagai dilindungi-tulis untuk membedakannya dari bidang terakhir biasa.

Sumber: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4

Stephan Markwalder
sumber
9

Saya juga mengintegrasikannya dengan joor library

Gunakan saja

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

Saya juga memperbaiki masalah overrideyang sepertinya terlewatkan oleh solusi sebelumnya. Namun gunakan ini dengan sangat hati-hati, hanya ketika tidak ada solusi bagus lainnya.

iirekm
sumber
Ketika saya mencoba ini (JDK12), saya mendapatkan pengecualian: "Tidak dapat mengatur bidang ___ akhir".
Aaron Iba
@AaronIba Tidak lagi diizinkan di Java 12+.
NateS
7

Bersamaan dengan jawaban berperingkat teratas Anda dapat menggunakan pendekatan yang paling sederhana. FieldUtilsKelas Apache commons sudah memiliki metode khusus yang dapat melakukan hal-hal tersebut. Tolong, lihat FieldUtils.removeFinalModifiermetode. 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 .

nndru
sumber
Ini adalah solusi yang jauh lebih sederhana daripada jawaban yang saat ini diterima
Bernie
4
Apakah itu? Menyalin satu metode terdengar seperti solusi yang lebih sederhana daripada mengimpor seluruh perpustakaan (yang melakukan hal yang sama dengan metode yang akan Anda salin).
eski
1
Tidak berfungsi di Java 12+:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR
6

Jika ada Manajer Keamanan, seseorang dapat menggunakan AccessController.doPrivileged

Mengambil contoh yang sama dari jawaban yang diterima di atas:

import java.lang.reflect.*;

public class EverythingIsTrue {
    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");

        // wrapping setAccessible 
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                modifiersField.setAccessible(true);
                return null;
            }
        });

        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);
      System.out.format("Everything is %s", false); // "Everything is true"
    }
}

Dalam ekspresi lambda AccessController.doPrivileged,, dapat disederhanakan untuk:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
Vanaga
sumber
1
Ini sepertinya juga tidak berfungsi dengan java 12+.
dan1st
ya @ dan1st, kamu benar! Silakan periksa ini untuk solusinya: stackoverflow.com/a/56043252/2546381
VanagaS
2

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.UnsafeQualifiedStaticObjectFieldAccessorImpldalam 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:

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

Namun untuk kasus umum ini tidak akan cukup.

Tomáš Záluský
sumber
2

Sekalipun finalsebuah 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:

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

Di Jawa, kelas terlihat berbicara kasar sebagai berikut:

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

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 :

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

Satu-satunya batasan yang diperiksa oleh JVM HotSpot adalah bahwa finalbidang tidak boleh dimodifikasi di luar kelas tempat finalbidang tersebut dideklarasikan.

Beberapa nama
sumber
0

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:

 /**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class SomeClass {

    private final String str;

    SomeClass(){
        this.str = "This is the string that never changes!";
    }

    public String getStr() {
        return str;
    }

    @Override
    public String toString() {
        return "Class name: " + getClass() + " Value: " + getStr();
    }
}

Beberapa kelas sederhana dengan variabel String akhir. Jadi di import java.lang.reflect.Field kelas utama;

/**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class Main {


    public static void main(String[] args) throws Exception{

        SomeClass someClass = new SomeClass();
        System.out.println(someClass);

        Field field = someClass.getClass().getDeclaredField("str");
        field.setAccessible(true);

        field.set(someClass, "There you are");

        System.out.println(someClass);
    }
}

Outputnya adalah sebagai berikut:

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Process finished with exit code 0

Menurut dokumentasi https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html

hasskell
sumber
Pernahkah Anda melihat posting ini ?
Ravindra HV
Pertanyaan ini menanyakan tentang staticbidang terakhir, jadi kode ini tidak berfungsi. setAccessible(true)hanya berfungsi untuk menyetel bidang instance akhir.
Radiodef
0

Jika bidang Anda hanya bersifat pribadi, Anda dapat melakukan ini:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

dan melempar / menangani NoSuchFieldException

Philip Rego
sumber
-4

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 finalsejak awal.

thecoop
sumber
4
Selain itu, finalmemiliki peran khusus dalam eksekusi multithreaded - mengubah finalnilai akan merusak model memori Java juga.
Péter Török
1
Dan bidang yang tidak dideklarasikan finaltidak boleh dinyatakan static.
Tom Hawtin - tackline
2
@ Tom: Secara umum itu mungkin benar, tapi saya tidak akan melarang semua variabel statis dapat berubah.
bcat
7
@ Tom: Apakah Anda pernah membaca mengapa lajang jahat? Aku melakukannya! Sekarang saya tahu bahwa mereka jahat hanya di Jawa. Dan hanya karena ketersediaan loader kelas yang ditentukan pengguna. Dan sejak saya tahu semua ini dan saya tidak menggunakan loader kelas yang ditentukan pengguna, saya menggunakan lajang tanpa menyesal. Dan begitu pula Scala di mana singleton adalah fitur bahasa kelas satu - Bahwa lajang jahat adalah mitos palsu yang terkenal .
Martin
3
@ Martin Saya tahu bahwa komentar Anda sudah tua, dan mungkin pandangan Anda telah berubah sekarang, tapi saya pikir saya akan menambahkan ini: Lajang jahat karena alasan yang tidak ada hubungannya dengan Jawa. Mereka menambahkan kompleksitas tersembunyi ke kode Anda. Selain itu, mereka dapat membuat mustahil untuk unit test tanpa juga mengetahui bahwa n lajang juga harus dikonfigurasi terlebih dahulu. Mereka adalah antitesis injeksi ketergantungan. Tim Anda mungkin membuat keputusan bahwa jebakan memiliki kompleksitas tersembunyi tidak melebihi kenyamanan Singletons, tetapi banyak tim mengambil sikap yang berlawanan karena alasan yang baik.
naksir