Zmień prywatne statyczne pole końcowe za pomocą odbicie Java


478

Mam zajęcia z private static finalpolem, które niestety muszę zmienić w czasie wykonywania.

Za pomocą odbicia otrzymuję ten błąd: java.lang.IllegalAccessException: Can not set static final boolean field

Czy jest jakiś sposób na zmianę wartości?

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

4
Taki zły pomysł. Zamiast tego spróbowałbym pobrać źródło i ponownie skompilować (lub nawet dekompilować / rekompilować).
Bill K

System.out jest publicznym statycznym polem końcowym, ale można go również zmienić.
niepodważalny

19
@irreputable System.out/in/errsą tak „wyjątkowe”, że Java Memory Model musi o nich szczególnie wspomnieć. Nie są to przykłady, którymi należy się kierować.
Tom Hawtin - tackline

8
Cóż, moim celem jest znaleźć hack pomiędzy, aby moja aplikacja działała, dopóki odpowiedzialna biblioteka nie dokona zmiany w następnym wydaniu, więc nie muszę już
hakować

1
@Bill K sprzed dziesięciu lat: rekompilacja byłaby WIELKA, ale jest ona na wdrożonym systemie i muszę ją tylko załatać, aż będziemy mogli zaktualizować wdrożoną aplikację!
Bill K

Odpowiedzi:


887

Zakładając, że nie przeszkadza SecurityManagerci to zrobić, możesz użyć setAccessibledo obejścia privatei zresetowania modyfikatora, aby się go pozbyć finali faktycznie zmodyfikować private static finalpole.

Oto przykład:

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

Zakładając, że nie SecurityExceptionzostanie wyrzucony, powyższy kod zostanie wydrukowany "Everything is true".

To, co faktycznie tutaj zrobiono, wygląda następująco:

  • Prymitywne booleanwartości truei falsew mainsą autoboxed do typu odniesienia Boolean„stałych” Boolean.TRUEiBoolean.FALSE
  • Odbicie służy do zmiany public static final Boolean.FALSEodniesienia do, Booleano którym mowa wBoolean.TRUE
  • W rezultacie, za każdym razem, gdy a falsejest autoboxowane Boolean.FALSE, odnosi się do tego samegoBoolean jak ten, zwaną przezBoolean.TRUE
  • Wszystko, co było "false"teraz, jest"true"

Powiązane pytania


Ostrzeżenia

Za każdym razem, gdy robisz coś takiego, należy zachować szczególną ostrożność. Może nie działać, ponieważ SecurityManagermoże być obecny, ale nawet jeśli nie działa, w zależności od wzorca użytkowania może działać lub nie.

JLS 17.5.3 Późniejsza modyfikacja pól końcowych

W niektórych przypadkach, takich jak deserializacja, system będzie musiał zmienić finalpola obiektu po zakończeniu budowy. finalpola można zmieniać za pomocą refleksji i innych środków zależnych od implementacji. Jedyny wzorzec, w którym ma to rozsądną semantykę, to taki, w którym obiekt jest konstruowany, a następnie finalpola obiektu są aktualizowane. Obiekt nie powinien być widoczny dla innych wątków, a finalpola nie powinny być odczytywane, dopóki wszystkie aktualizacje finalpól obiektu nie zostaną zakończone. Zamrożenie finalpola występuje zarówno na końcu konstruktora, w którym finalpole jest ustawione, jak i natychmiast po każdej modyfikacji finalpola poprzez odbicie lub inny specjalny mechanizm.

Nawet wtedy istnieje wiele komplikacji. Jeśli finalpole zostanie zainicjowane do stałej czasu kompilacji w deklaracji pola, zmiany w finalpolu mogą nie zostać zaobserwowane, ponieważ użycie tego finalpola jest zastępowane w czasie kompilacji stałą czasu kompilacji.

Innym problemem jest to, że specyfikacja pozwala na agresywną optymalizację finalpól. W obrębie wątku dopuszcza się zmianę kolejności odczytów finalpola z tymi modyfikacjami pola końcowego, które nie mają miejsca w konstruktorze.

Zobacz też

  • JLS 15.28 Wyrażenie stałe
    • Jest mało prawdopodobne, aby ta technika działała z prymitywem private static final boolean, ponieważ jest ona nieodłączna jako stała czasowa kompilacji, a zatem „nowa” wartość może nie być obserwowalna

Dodatek: o manipulacji bitowej

Głównie,

field.getModifiers() & ~Modifier.FINAL

wyłącza bit odpowiadający Modifier.FINALod field.getModifiers(). &jest bitowo-i, i ~jest bit-dopełniaczem.

Zobacz też


Pamiętaj o stałych wyrażeniach

Nadal nie jesteś w stanie rozwiązać tego ?, popadłeś w depresję tak jak ja? Czy twój kod wygląda tak?

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

Czytając komentarze do tej odpowiedzi, szczególnie tej autorstwa @Pshemo, przypomniałem sobie, że Wyrażenia stałe są obsługiwane inaczej, więc nie będzie można ich modyfikować. Dlatego musisz zmienić kod, aby wyglądał następująco:

public class A {
    private final String myVar;

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

jeśli nie jesteś właścicielem klasy ... Czuję cię!

Aby uzyskać więcej informacji o tym, dlaczego to zachowanie czyta to ?


41
@thecoop, @HalfBrian: nie ma wątpliwości, że jest to niezwykle negatywnym , ale ten przykład został wybrany przez konstrukcji. Moja odpowiedź pokazuje tylko, jak w niektórych okolicznościach jest to możliwe. Najbardziej obrzydliwy przykład, jaki mogę wymyślić, to celowy wybór z nadzieją, że być może ludzie natychmiast by się odrazili, zamiast zakochać się w tej technice.
polygenelubricants

59
Hej, stary. Słyszałem, że lubisz odbicie, więc zastanawiałem się nad polem, abyś mógł odbijać, gdy ty odbijasz.
Matthew Flaschen

11
Zauważ, że Boolean.FALSE nie jest prywatny. Czy to naprawdę działa z członkami „private final static”?
mgaert

15
@mgaert to robi, ale trzeba użyć getDeclaredField()zamiast getField()do klasy docelowej
EIS

10
+1. Dla tych, którzy spróbują zmienić coś podobnego final String myConstant = "x";i nie powiedzie się: pamiętaj, że kompilator wstawi stałe czasowe kompilacji, więc kiedy napiszesz taki kod System.out.println(myConstant);, zostanie on skompilowany, System.out.println("x");ponieważ kompilator zna wartość stałej w czasie kompilacji. Aby pozbyć się tego problemu, musisz zainicjować swoje stałe w czasie wykonywania, np final String myConstant = new String("x");. Również w przypadku prymitywów takich jak final int myField = 11użycie final int myField = new Integer(11);lubfinal Integer myField = 11;
Pshemo

58

Jeśli wartość przypisana do static final booleanpola jest znana w czasie kompilacji, jest to stała. Pola pierwotne lub String typu mogą być stałymi czasu kompilacji. Stała zostanie wstawiona w każdym kodzie, który odwołuje się do pola. Ponieważ pole nie jest w rzeczywistości odczytywane w czasie wykonywania, zmiana tego pola nie przyniesie żadnego efektu.

Specyfikacja języka Java mówi:

Jeśli pole jest zmienną stałą (§4.12.4), wówczas usunięcie słowa kluczowego final lub zmiana jego wartości nie zakłóci zgodności z wcześniej istniejącymi plikami binarnymi, powodując, że nie będą działać, ale nie zobaczą żadnej nowej wartości dla użycia pola, chyba że zostaną ponownie skompilowane. Jest to prawdą, nawet jeśli samo użycie nie jest wyrażeniem stałym czasu kompilacji (§15.28)

Oto przykład:

class Flag {
  static final boolean FLAG = true;
}

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

Jeśli dekompilujesz Checker, zobaczysz, że zamiast odwoływania się Flag.FLAG, kod po prostu wypycha wartość 1 ( true) na stos (instrukcja nr 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

To była moja pierwsza myśl, ale potem przypomniałem sobie Javę skompilowaną w czasie wykonywania, jeśli zresetujesz bit, po prostu rekompiluje się z nim jako zmienną zamiast stałej.
Bill K

4
@Bill K - Nie, to nie dotyczy kompilacji JIT. Pliki klas zależnych będą faktycznie zawierały wartości wstawione i nie będą zawierały odniesienia do niezależnej klasy. Testowanie jest dość prostym eksperymentem; Dodam przykład.
erickson

1
Jak to jive z odpowiedzią @polygenelubricants, w której redefiniuje Boolean.false? - ale masz rację, widziałem to zachowanie, gdy rzeczy nie kompilowały się poprawnie.
Bill K

26
@ Bill K - w odpowiedzi na polygenlubricants pole nie jest stałą czasową kompilacji. To public static final Boolean FALSE = new Boolean(false)nie jestpublic static final boolean FALSE = false
erickson,

17

Trochę ciekawości ze specyfikacji języka Java, rozdział 17, sekcja 17.5.4 „Pola chronione przed zapisem”:

Zwykle pole, które jest ostateczne i statyczne, nie może być modyfikowane. Jednak System.in, System.out i System.err są statycznymi polami końcowymi, które ze względu na starsze wersje muszą być dopuszczone do zmiany metodami System.setIn, System.setOut i System.setErr. Te pola nazywamy chronionymi przed zapisem, aby odróżnić je od zwykłych pól końcowych.

Źródło: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4


9

Zintegrowałem go również z biblioteką joor

Po prostu użyj

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

Naprawiłem także problem, z overridektórym wydaje się, że brakuje poprzednich rozwiązań. Jednak używaj tego bardzo ostrożnie, tylko gdy nie ma innego dobrego rozwiązania.


Gdy próbuję tego (JDK12), pojawia się wyjątek: „Nie można ustawić końcowego pola ___”.
Aaron Iba

@AaronIba Nie jest już dozwolone w Javie 12+.
NateS

7

Wraz z odpowiedzią na najwyższym miejscu możesz zastosować nieco najprostsze podejście. FieldUtilsKlasa Apache commons ma już określoną metodę, która potrafi to zrobić. Proszę spojrzeć na FieldUtils.removeFinalModifiermetodę. Należy określić docelową instancję pola i flagę wymuszania dostępności (jeśli grasz z polami niepublicznymi). Więcej informacji można znaleźć tutaj .


Jest to o wiele prostsze rozwiązanie niż obecnie akceptowana odpowiedź
Bernie

4
Czy to jest Kopiowanie jednej metody brzmi jak prostsze rozwiązanie niż importowanie całej biblioteki (która robi to samo co metoda, którą kopiujesz).
eski

1
Nie działa w Javie 12+:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR

6

W przypadku obecności Security Managera można skorzystać z AccessController.doPrivileged

Biorąc ten sam przykład z zaakceptowanej odpowiedzi powyżej:

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

W wyrażeniu lambda AccessController.doPrivilegedmożna uprościć:

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

1
Wydaje się to również nie działać z java 12+.
dan1

tak @ dan1st, masz rację! Sprawdź to, aby znaleźć rozwiązanie: stackoverflow.com/a/56043252/2546381
VanagaS

2

Przyjęta odpowiedź działała dla mnie do momentu wdrożenia na JDK 1.8u91. Potem zdałem sobie sprawę, że nie powiodło się na field.set(null, newValue);linii, kiedy odczytałem wartość przez odbicie przed wywołaniem setFinalStaticmetody.

Prawdopodobnie odczyt spowodował w jakiś sposób inną konfigurację wewnętrznych elementów odbicia Java (mianowicie sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImplw przypadku niepowodzenia zamiast sun.reflect.UnsafeStaticObjectFieldAccessorImplsukcesu), ale nie rozwinąłem go dalej.

Ponieważ musiałem tymczasowo ustawić nową wartość w oparciu o starą wartość, a później cofnąć starą wartość, nieco zmieniłem podpis, aby udostępnić funkcję obliczeniową na zewnątrz, a także zwrócić starą wartość:

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

Jednak w ogólnym przypadku nie byłoby to wystarczające.


2

Nawet pomimo tego, że finalpole może być modyfikowane poza statycznym inicjatorem i (przynajmniej JVM HotSpot) doskonale wykona kod bajtowy.

Problem polega na tym, że kompilator Java nie pozwala na to, ale można to łatwo obejść za pomocą objectweb.asm. Oto doskonale poprawny plik klasy, który przechodzi weryfikację kodu bajtowego i pomyślnie załadował i zainicjował w 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();

W Javie klasa wygląda mniej więcej w następujący sposób:

public class Cl{
    private static final int fld;

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

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

których nie można skompilować javac, ale można je załadować i wykonać za pomocą JVM.

JVM HotSpot ma specjalne traktowanie takich klas w tym sensie, że uniemożliwia takim „stałym” udział w ciągłym zwijaniu. Ta kontrola jest wykonywana w fazie przepisywania kodu bajtowego inicjalizacji klasy :

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

Jedynym ograniczeniem, które sprawdza JVM HotSpot, jest to, że finalpole nie powinno być modyfikowane poza klasą, w której finaldeklarowane jest pole.


0

Właśnie zobaczyłem to pytanie w jednym z pytań podczas rozmowy kwalifikacyjnej, jeśli to możliwe, aby zmienić zmienną końcową za pomocą refleksji lub w czasie wykonywania. Zainteresowało mnie to, czym się stałem:

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

Niektóre proste klasy z końcową zmienną String. Tak więc w głównej klasie import 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);
    }
}

Dane wyjściowe będą następujące:

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

Zgodnie z dokumentacją https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html


Widziałeś ten post?
Ravindra HV

To pytanie dotyczy staticostatniego pola, więc ten kod nie działa. setAccessible(true)działa tylko w przypadku ustawiania pól instancji końcowej.
Radiodef,

0

Jeśli twoje pole jest po prostu prywatne, możesz to zrobić:

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

i wrzuć / obsłuż NoSuchFieldException


-4

Cały punkt a final pola polega na tym, że nie można go ponownie przypisać po ustawieniu. JVM korzysta z tej gwarancji, aby zachować spójność w różnych miejscach (np. Klasy wewnętrzne odwołujące się do zmiennych zewnętrznych). Więc nie. Możliwość tego złamałaby JVM!

Rozwiązaniem jest nie deklarowanie tego finalw pierwszej kolejności.


4
Co więcej, finalodgrywa szczególną rolę w wykonywaniu wielowątkowym - zmiana finalwartości złamałaby również model pamięci Java.
Péter Török

1
A pole niezadeklarowane finalnie powinno być deklarowane static.
Tom Hawtin - tackline

2
@Tom: Zasadniczo jest to prawdopodobnie prawda, ale nie zakazałbym wszystkich zmiennych zmiennych statycznych.
bcat

7
@Tom: Czy czytałeś kiedyś, dlaczego singletony są złe? Zrobiłem! Teraz wiem, że są źli tylko w Javie. I tylko ze względu na dostępność modułu ładującego zdefiniowanego przez użytkownika. I odkąd wiem to wszystko i nie używam modułu ładującego zdefiniowanego przez użytkownika, bez żalu używam singletonów. Podobnie Scala, gdzie singletony są pierwszorzędną cechą językową - że singletony są złe, jest dobrze znanym fałszywym mitem .
Martin

3
@Martin Wiem, że twój komentarz jest stary i być może twoje poglądy się zmieniły, ale pomyślałem, że dodam to: Singletony są złe z powodów, które nie mają nic wspólnego z Javą. Dodają ukrytą złożoność do twojego kodu. Dodatkowo mogą uniemożliwić przeprowadzenie testu jednostkowego, nie wiedząc również, że n singletonów również musi zostać skonfigurowanych w pierwszej kolejności. Są antytezą zastrzyku uzależnienia. Twój zespół może podjąć decyzję, że pułapki związane z ukrytą złożonością nie przeważają nad wygodą Singletonów, ale wiele drużyn ma słuszne stanowisko przeciwnie.
zmiażdżyć
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.