Zmiana prywatnego statycznego pola końcowego przy użyciu Java reflection

Mam klasę z polem private static final, które niestety muszę zmienić w czasie wykonywania.

Używając reflection dostaję 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);
Author: smholloway, 2010-07-21

9 answers

Zakładając, że żadne SecurityManager nie uniemożliwia ci tego, możesz użyć setAccessible, aby ominąć private i zresetować modyfikator, aby pozbyć się final, a właściwie zmodyfikować pole private static final.

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 zostanie wyrzucone SecurityException, powyższy kod drukuje "Everything is true".

To, co się tutaj dzieje, wygląda następująco:

  • podstawowe boolean wartości true i false W main są przypisane do typu odniesienia Boolean "Stałe" Boolean.TRUE i Boolean.FALSE
  • odbicie służy do zmiany public static final Boolean.FALSE aby odnieść się do Boolean, o którym mowa przez Boolean.TRUE
  • w rezultacie za każdym razem, gdy false jest autoboxowany w Boolean.FALSE, odwołuje się do tego samego Boolean, co ten, do którego odwołuje się Boolean.TRUE
  • Wszystko co było "false" Teraz jest "true"

Podobne pytania


Caveats

Należy zachować szczególną ostrożność, gdy robisz coś takiego. Może nie działać, ponieważ SecurityManager może być obecny, ale nawet jeśli nie, w zależności od wzorca użycia, może lub nie może działać.

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

W niektórych przypadkach, takich jak deserializacja, system będzie musiał zmienić final pola obiektu po zbudowaniu. final pola mogą być zmieniane za pomocą refleksji i innych środków zależnych od implementacji. Jedyny wzorzec, w którym ma to sensowną semantykę, to taki, w którym obiekt jest konstruowany, a następnie pola final obiektu są aktualizowane. Obiekt nie powinien być widoczny dla innych wątków, ani nie należy odczytywać pól final, dopóki wszystkie aktualizacje pól final obiektu nie zostaną zakończone. Zamrożenie pola final następuje zarówno na końcu konstruktora, w którym ustawione jest pole final, jak i bezpośrednio po każdej modyfikacji pola final poprzez odbicie lub inny specjalny mechanizm.

Nawet wtedy, istnieje wiele komplikacji. Jeśli pole final jest zainicjalizowane stałą czasu kompilacji w deklaracji pola, zmiany w polu final nie mogą być obserwowane, ponieważ użycie tego pola final jest zastępowane w czasie kompilacji stałą czasu kompilacji.

Innym problemem jest to, że specyfikacja pozwala na agresywną optymalizację pól final. W wątku dopuszczalne jest zmiana kolejności odczytów pola final z tymi modyfikacjami końcowego pola, 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 prymitywnym private static final boolean, ponieważ jest zapisywana jako stała czasu kompilacji, a tym samym " Nowa" wartość może nie być obserwowalna

Dodatek: o manipulacji bitowej

Zasadniczo,

field.getModifiers() & ~Modifier.FINAL

Wyłącza bit odpowiadający Modifier.FINAL z field.getModifiers(). & jest bitowym-I, oraz ~ jest bitowym-dopełnieniem.

Zobacz też


Zapamiętaj Wyrażenia Stałe

Nadal nie jesteś w stanie tego rozwiązać?, popadły w depresję jak ja zrobiłeś to? 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 przez @Pshemo, przypomniało mi się, że wyrażenia stałe są traktowane inaczej, więc będzie niemożliwe, aby je zmodyfikować. Dlatego będziesz musiał zmienić swój kod, aby wyglądał tak:

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 przeczytaj to ?

 742
Author: polygenelubricants,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-06 06:27:57

Jeśli wartość przypisana do pola static final boolean jest znana w czasie kompilacji, jest to stała . pola prymitywne lub String Typ może być stałymi czasu kompilacji. Stała będzie inlined w każdym kodzie, który odwołuje się do pola. Ponieważ pole nie jest w rzeczywistości odczytywane w czasie wykonywania, jego zmiana nie będzie miała żadnego wpływu.

Specyfikacja języka Java mówi tak:

Jeśli pole jest stałą zmienną (§4.12.4), a następnie usunięcie słowa kluczowego finał zmiana jego wartości nie będzie zerwać kompatybilność z istniejącymi wcześniej binaria, powodując, że nie uruchamiają się, ale nie zobaczą żadnej nowej wartości za korzystanie z pola, chyba że są rekompilowane. to prawda, nawet jeśli samo użycie nie jest czasem kompilacji wyrażenie stałe (§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ływać się do Flag.FLAG, kod po prostu wypycha wartość 1 (true) na stos (Instrukcja # 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
 46
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-05 17:32:08

Mała ciekawostka 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że, System.in, System.out, i System.err są statycznymi polami końcowymi które, ze względów historycznych, muszą być dopuszczone do zmiany za pomocą metod System.setIn, System.setOut i System.setErr. Odnosimy się do tych pola jako zabezpieczone przed zapisem w celu odróżnienia ich od zwykłych finał pola.

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

 12
Author: Stephan Markwalder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-21 19:21:18

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

Po prostu użyj

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

Naprawiłem również problem z override, który wydaje się brakować w poprzednich rozwiązaniach. Jednak używaj tego bardzo ostrożnie, tylko wtedy, gdy nie ma innego dobrego rozwiązania.

 4
Author: iirekm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-31 01:06:32

W przypadku obecności menedżera bezpieczeństwa 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.doPrivileged, można uprościć do:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
 4
Author: VanagaS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-25 06:06:43

Wraz z najwyżej ocenioną odpowiedzią możesz użyć nieco prostszego podejścia. Klasa Apache commons FieldUtils ma już określoną metodę, która może to robić. Proszę spojrzeć na metodę FieldUtils.removeFinalModifier. Należy określić instancję pola docelowego i flagę wymuszania dostępności (jeśli grasz z polami niepublicznymi). Więcej informacji można znaleźć TUTAJ .

 4
Author: nndru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-07 10:32:39

Zaakceptowana odpowiedź działała dla mnie do momentu wdrożenia na JDK 1. 8u91. Potem zdałem sobie sprawę, że nie powiodło się w linii field.set(null, newValue);, Kiedy odczytałem wartość przez odbicie przed wywołaniem metody setFinalStatic.

Prawdopodobnie odczyt spowodował nieco inną konfigurację wewnętrznych odbić Javy (mianowicie sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl w przypadku niepowodzenia zamiast sun.reflect.UnsafeStaticObjectFieldAccessorImpl w przypadku sukcesu), ale nie rozwinąłem tego dalej.

Ponieważ musiałem tymczasowo ustawić nową wartość na podstawie starej wartości, a później ustawić starą wartość z powrotem, zmieniłem podpis trochę, aby zapewnić funkcję obliczeniową zewnętrznie, 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że w przypadku ogólnym nie byłoby to wystarczające.

 2
Author: Tomáš Záluský,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-10 12:45:19

Właśnie widziałem to pytanie na jednym z pytań wywiadu, jeśli to możliwe, aby zmienić ostateczną zmienną z odbiciem lub w uruchomieniu. Bardzo mnie to zainteresowało, więc 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();
    }
}

Jakaś prosta klasa z końcową zmienną łańcuchową. Więc w głównej klasie Importuj Javę.lang.zastanów się.Pole;

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

Wynik będzie następujący:

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

 -1
Author: hasskell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-04 09:21:27

Cały punkt pola final jest taki, że nie można go ponownie przypisać raz ustawione. JVM używa tej gwarancji do zachowania spójności w różnych miejscach (np. klasy wewnętrzne odwołujące się do zmiennych zewnętrznych). Więc nie. Być w stanie to zrobić złamie JVM!

Rozwiązaniem nie jest zadeklarowanie go final w pierwszej kolejności.

 -5
Author: thecoop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-21 16:38:55