Java yansımasını kullanarak özel statik son alanı değiştirme


479

Bir dersim var private static finalMaalesef çalışma zamanında değiştirmem gereken alanı olan .

Yansıma kullanarak bu hatayı alıyorum: java.lang.IllegalAccessException: Can not set static final boolean field

Değeri değiştirmenin bir yolu var mı?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);

4
Çok kötü bir fikir. Ben kaynak almak ve onun yerine yeniden derlemek (hatta decompile / recompile) denemek istiyorum.
Bill K

System.out genel bir statik son alandır, ancak değiştirilebilir.
dayanılmaz

19
@irreputable System.out/in/erro kadar "özel" ki Java Bellek Modeli bunlardan özel olarak bahsetmek zorunda. İzlenmesi gereken örnekler değildir.
Tom Hawtin - tackline

8
iyi benim nokta ws arasında benim app çalışan lib sorumlu bir sonraki sürümde değişiklik yapmak kadar çalışmak için arasında bir kesmek bulmak için artık kesmek gerek yok ...
fixitagain

1
@ Fatura K on yıl önce: Yeniden derlemek BÜYÜK olurdu ama konuşlandırılmış bir sistemde ve konuşlandırılan uygulamayı güncelleyene kadar onu düzeltmem gerekiyor!
Bill K

Yanıtlar:


888

Hiçbir varsayarsak SecurityManagerbunu engelliyor kullanabileceğiniz setAccessibleaşmanın privateve kurtulmak için değiştirici sıfırlama finalve aslında bir değiştirme private static finalalanı.

İşte bir örnek:

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"
   }
}

Hayır SecurityExceptionatıldığı varsayıldığında , yukarıdaki kod yazdırılır "Everything is true".

Aslında burada yapılanlar şöyledir:

  • İlkel booleandeğerleri trueve falsede mainbir referans tipi autoboxed edilir Boolean"sabitler" Boolean.TRUEveBoolean.FALSE
  • Yansıma, public static final Boolean.FALSEatıfta Booleanbulunulan referansı değiştirmek için kullanılır.Boolean.TRUE
  • Sonuç olarak, daha sonra a otomatik olarak her falsekutuya yerleştirildiğinde Boolean.FALSE,Boolean bir göre olarak kabul içinBoolean.TRUE
  • "false"Şimdi olan her şey"true"

İlgili sorular


Uyarılar

Böyle bir şey yaptığınızda çok dikkatli olunmalıdır. Çalışmayabilir, çünkü bir var SecurityManagerolabilir, ancak kullanım şekline bağlı olarak çalışmasa bile çalışabilir veya çalışmayabilir.

JLS 17.5.3 Son Alanlarda Sonraki Değişiklikler

Deserialization gibi bazı durumlarda, sistemin finalinşaattan sonra bir nesnenin alanlarını değiştirmesi gerekecektir . finalalanlar yansıma ve uygulamaya bağlı diğer araçlarla değiştirilebilir. Bunun makul semantiğe sahip olduğu tek desen, bir nesnenin oluşturulduğu ve daha sonra finalnesnenin alanlarının güncellendiği modeldir . Nesnenin finalalanlarındaki tüm güncelleştirmeler finaltamamlanıncaya kadar, nesnenin diğer iş parçacıklarına görünür kılınmaması veya alanların okunması gerekir . Bir finalalanın donması, hem finalalanın kurulduğu yapıcı sonunda hem de bir alanın her değişiklikten hemen sonra finalyansıma veya diğer özel mekanizmalarla gerçekleşir.

O zaman bile, bir takım komplikasyonlar var. Bir finalalan, alan bildiriminde bir derleme zamanı sabitine başlatılırsa, bu alanın kullanımları derleme zamanı sabiti ile derleme zamanında değiştirildiği için, finalalandaki değişiklikler gözlenmeyebilir final.

Başka bir sorun, spesifikasyonun finalalanların agresif optimizasyonuna izin vermesidir . Bir iş parçacığı içinde, bir finalalanın okumalarını , yapıcıda yer almayan bir son alanın bu değişiklikleriyle yeniden sıralamak mümkündür .

Ayrıca bakınız

  • JLS 15.28 Sürekli İfade
    • Bu tekniğin bir ilkel ile çalışması olası değildir private static final boolean, çünkü derleme zamanı sabiti olarak inline edilemez ve bu nedenle "yeni" değer gözlemlenebilir olmayabilir

Ek: Bitsel manipülasyonda

esasen,

field.getModifiers() & ~Modifier.FINAL

Biraz tekabül kapatır Modifier.FINALden field.getModifiers(). &bitsel-ve ve ~bitsel-tamamlayıcıdır.

Ayrıca bakınız


Sabit İfadeleri Hatırla

Hala bunu çözememek mümkün mü?, Benim için olduğu gibi depresyona düştünüz mü? Kodunuz böyle mi görünüyor?

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

Bu cevaptaki, özellikle de @Pshemo'nun yorumlarını okuduğumda, Sabit İfadelerin farklı ele alındığı hatırlatıldı, bu yüzden değiştirmek imkansız olacak . Bu nedenle, aşağıdaki gibi görünmesi için kodunuzu değiştirmeniz gerekecektir:

public class A {
    private final String myVar;

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

sınıfın sahibi değilseniz ... Seni hissediyorum!

Bu davranışın neden bunu okuduğu hakkında daha fazla bilgi için ?


41
@thecoop, @HalfBrian: Bunun son derece kötü olduğuna şüphe yok , ancak bu örnek tasarım tarafından seçildi. Cevabım sadece bazı durumlarda bunun nasıl mümkün olabileceğini gösteriyor. Düşünebildiğim en iğrenç örnek, belki de insanların tekniğe aşık olmak yerine anında tiksineceği umuduyla kasıtlı olarak seçildi.
polygenelubricants

59
Hey, kahretsin. Seni yansıma gibi duydum, bu yüzden alana yansıtıyorum, böylece yansıtırken yansıtabilirsin.
Matthew Flaschen

11
Boolean.FALSE özel değildir. Bu gerçekten "özel son statik" üyeleri ile çalışır mı?
mgaert

15
@mgaert, ama hedef sınıf getDeclaredField()yerine kullanmak zorundasınızgetField()
eis

11
+1. Gibi değişim şey çalışacağım olanlar için final String myConstant = "x";bu derleme zamanı sabitleri gibi kod yazmak ne zaman bu kadar derleyici tarafından satır içine yerleştirilmiş olacaktır unutmayın: ve başarısız olur System.out.println(myConstant);o kadar derlenmiş olacak System.out.println("x");derleyici derleme zamanında sabit değerini bildiği için. Bu sorundan kurtulmak için sabitlerinizi çalışma zamanında başlatmanız gerekir final String myConstant = new String("x");. Ayrıca final int myField = 11kullanım gibi ilkellerde final int myField = new Integer(11);veyafinal Integer myField = 11;
Pshemo

58

Bir static final booleanalana atanan değer derleme zamanında biliniyorsa, sabittir. İlkel veya String tür derleme zamanı sabitleri olabilir. Bir sabit, alana başvuran herhangi bir kodda satır içine alınır. Alan çalışma zamanında gerçekte okunmadığından, alanın değiştirilmesinin bir etkisi olmayacaktır.

Java dili spesifikasyonu bu diyor ki:

Bir alan sabit bir değişkendirse (§4.12.4), son anahtar kelimenin silinmesi veya değerinin değiştirilmesi önceden çalıştırılmamış ikili dosyalar ile çalışmamalarına neden olarak uyumluluğu bozmaz, ancak kullanım için yeni bir değer görmez yeniden derlenmedikçe alanın Kullanımın kendisi derleme zamanı sabit ifadesi olmasa bile bu geçerlidir (§15.28)

İşte bir örnek:

class Flag {
  static final boolean FLAG = true;
}

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

Eğer koda dönüştürürseniz Checker, referans vermek yerine Flag.FLAGkodun trueyığının üzerine 1 ( ) değerini ittiğini göreceksiniz (talimat # 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

Bu benim ilk düşüncemdi, ama sonra Java'yı zamanında derledim, biti sıfırlarsanız, sabit yerine değişken olarak yeniden derleyecekti.
Bill K

4
@ Fatura K - Hayır, bu JIT derlemesine işaret etmez. Bağımlı sınıf dosyaları aslında satır içi değerleri içerecek ve bağımsız sınıfa başvuru olmayacaktır. Test etmek oldukça basit bir deney; Bir örnek ekleyeceğim.
erickson

1
Boolean.false'yi yeniden tanımladığı yerde @polygenelubricants'ın cevabı ile bu jive nasıl? - Ama haklısın, işler doğru derlenmediğinde bu davranışı gördüm.
Bill K

26
@ Fatura K - poligenlubrikantların cevabında, alan derleme zaman sabiti değildir. O var public static final Boolean FALSE = new Boolean(false)değilpublic static final boolean FALSE = false
erickson

17

Java Dil Spesifikasyonu, bölüm 17, bölüm 17.5.4 "Yazma Korumalı Alanlar" dan biraz merak:

Normal olarak, son ve statik olan bir alan değiştirilemeyebilir. Ancak, System.in, System.out ve System.err, eski nedenlerle System.setIn, System.setOut ve System.setErr yöntemleriyle değiştirilmesine izin verilmesi gereken statik son alanlardır. Bu alanlardan, normal son alanlardan ayırt edilebilmeleri için yazmaya karşı korumalı olarak bahsediyoruz.

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


9

Ayrıca joor kütüphanesi ile entegre ettim

Sadece kullan

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

Ayrıca overrideönceki çözümlerin kaçırdığı bir sorunu düzelttim . Ancak bunu çok dikkatli kullanın, ancak başka iyi bir çözüm olmadığında.


Bunu (JDK12) denediğimde, bir istisna alıyorum: "Son ___ alan ayarlanamıyor".
Aaron Iba

@AaronIba Artık Java 12+ sürümlerinde izin verilmiyor.
NateS

7

En üst sıradaki cevapla birlikte biraz daha basit bir yaklaşım kullanabilirsiniz. Apache commons FieldUtilssınıfı, zaten işleri yapabilen belirli bir yönteme sahiptir. Lütfen FieldUtils.removeFinalModifieryönteme bakınız. Hedef alan örneğini ve erişilebilirlik zorlama bayrağını belirtmelisiniz (herkese açık olmayan alanlarla oynuyorsanız). Daha fazla bilgiyi burada bulabilirsiniz .


Bu, şu anda kabul edilen cevaptan çok daha basit bir çözümdür
Bernie

4
Bu mu? Bir yöntemi kopyalamak, tüm kitaplığı içe aktarmaktan daha basit bir çözüm gibi görünür (kopyaladığınız yöntemle aynı şeyi yapar).
eski

1
Java 12+ ile çalışmaz:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR

6

Bir Güvenlik Yöneticisinin bulunması durumunda, AccessController.doPrivileged

Aynı örneği yukarıdaki kabul edilen cevaptan alarak:

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"
    }
}

Lambda ifadesinde, AccessController.doPrivilegedbasitleştirilebilir:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});

1
Bu da java 12+ ile çalışmıyor gibi görünüyor.
dan1st

evet @ dan1st, haklısın! Lütfen bir çözüm olup olmadığını kontrol edin: stackoverflow.com/a/56043252/2546381
VanagaS

2

Kabul edilen cevap, JDK 1.8u91'de dağıtılana kadar benim için çalıştı. Daha sonra field.set(null, newValue);çağırmadan önce değeri yansıma yoluyla okuduğumda hatta başarısız olduğunu fark ettim .setFinalStatic yöntemi .

Muhtemelen okuma, bir şekilde farklı Java yansıma iç kurulumuna neden olmuştur (yani sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl, başarısız durumda,sun.reflect.UnsafeStaticObjectFieldAccessorImpl başarı durumunda başarısız durumda), ancak daha fazla ayrıntı vermedim.

Geçici olarak eski değere dayalı olarak yeni bir değer belirlemem ve daha sonra eski değeri geri ayarlamam gerektiğinden, harici olarak hesaplama işlevini sağlamak ve ayrıca eski değeri döndürmek için imzayı biraz değiştirdim:

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;
    } ...

Ancak genel durum için bu yeterli olmaz.


2

finalBir alan olmasına rağmen bile statik başlatıcı dışında değiştirilebilir ve (en azından JVM HotSpot) bayt kodunu mükemmel şekilde çalıştıracaktır.

Sorun, Java derleyicisinin buna izin vermemesi, ancak bu kullanılarak kolayca atlanabilir objectweb.asm. Bayt kodu doğrulamasını geçen ve JVM HotSpot OpenJDK12 altında başarıyla yüklenen ve başlatılan mükemmel geçerli bir sınıf dosyası:

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();

Java'da, sınıf kabaca aşağıdaki gibi görünür:

public class Cl{
    private static final int fld;

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

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

bunlar derlenemez javac, ancak JVM tarafından yüklenebilir ve yürütülebilir.

JVM HotSpot, bu tür "sabitlerin" sürekli katlanmaya katılmasını engellediği için bu tür sınıflara özel muameleye sahiptir. Bu kontrol, sınıf başlatmanın bayt kodu yeniden yazma aşamasında yapılır :

// 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);
            }
          }
        }
      }
    }
}

JVM HotSpot'un kontrol ettiği tek kısıtlama final, finalalanın bildirildiği sınıfın dışında değiştirilmemesidir .


0

Son soruyu yansıma veya çalışma zamanında değiştirmek için mümkünse, bu soruyu görüşme sorusundan birinde gördünüz. Gerçekten ilgilendim, böylece ne oldum:

 /**
 * @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();
    }
}

Son String değişkenli bazı basit sınıflar. Yani ana sınıfta içe aktar java.lang.reflect.Field;

/**
 * @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);
    }
}

Çıktı aşağıdaki gibi olacaktır:

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

Belgelere göre https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html


Gördüğünüz bu yazı?
Ravindra HV

Bu soru bir staticson alan sorar , bu nedenle bu kod çalışmaz. setAccessible(true)yalnızca son örnek alanlarını ayarlamak için çalışır.
Radiodef

0

Alanınız yalnızca özelse bunu yapabilirsiniz:

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

ve NoSuchFieldException komutunu / işlemesini gerçekleştirin


-4

Bütün mesele final alanın ayarlandıktan sonra yeniden atanamayacağıdır. JVM bu garantiyi çeşitli yerlerde tutarlılığı korumak için kullanır (örneğin dış değişkenleri referans alan iç sınıflar). Yani hayır. Bunu yapabilmek JVM'yi kırabilir!

Çözüm, finalilk etapta ilan etmemek .


4
Ayrıca, finalçok iş parçacıklı yürütmede özel bir rol vardır - değişen finaldeğerler Java bellek modelini de bozacaktır.
Péter Török

1
Ve bildirilmeyen bir alan finalbildirilmemelidir static.
Tom Hawtin - tackline

2
@Tom: Genel olarak bu muhtemelen doğrudur, ancak tüm statik değişken değişkenleri yasaklamaz .
bcat

7
@Tom: Hiç singletonların neden kötü olduğunu okudun mu? Yaptım! Artık sadece Java'da kötülük yaptıklarını biliyorum. Ve sadece kullanıcı tanımlı sınıf yükleyicinin kullanılabilirliği nedeniyle. Ve tüm bunları bildiğimden ve kullanıcı tanımlı sınıf yükleyicisini kullanmadığımdan beri pişmanlık duymadan singleton kullanıyorum. Ve singleton'un birinci sınıf bir dil özelliği olduğu Scala da öyle - singletonların şeytani olduğu iyi bilinen bir yanlış efsanedir .
Martin

3
@Martin Yorumunuzun eski olduğunu biliyorum ve belki de görüşleriniz şimdiye kadar değişti, ama sadece bunu ekleyeceğimi düşündüm: Singletons, Java ile ilgisi olmayan nedenlerden dolayı kötüydü. Kodunuza gizli karmaşıklık eklerler . Ek olarak, önce n tektonun da yapılandırılması gerektiğini bilmeden birim testi yapmayı imkansız hale getirebilirler . Bunlar bağımlılık enjeksiyonunun antitezidir. Ekibiniz, gizli karmaşıklığa sahip olmanın tuzaklarının Singletons'un rahatlığından daha ağır basmadığına karar verebilir , ancak birçok takım iyi bir nedenden ötürü ters bir tavır alır.
ezmek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.