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);
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
.
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ścitrue
ifalse
Wmain
są przypisane do typu odniesieniaBoolean
"Stałe"Boolean.TRUE
iBoolean.FALSE
- odbicie służy do zmiany
public static final Boolean.FALSE
aby odnieść się doBoolean
, o którym mowa przezBoolean.TRUE
- w rezultacie za każdym razem, gdy
false
jest autoboxowany wBoolean.FALSE
, odwołuje się do tego samegoBoolean
, co ten, do którego odwołuje sięBoolean.TRUE
- Wszystko co było
"false"
Teraz jest"true"
Podobne pytania
-
używanie odbicia do zmiany
static final File.separatorChar
do testów jednostkowych -
jak ograniczyć setAccessible tylko do "legalne" zastosowania?
- W tym samym roku, w 2008 roku, w ramach projektu "the best of the World", w 2009 roku, w ramach projektu "the best of the World", w 2009 roku, w ramach projektu "the best of the World", w 2009 roku, w ramach projektu " the best of the world]}
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 polafinal
obiektu są aktualizowane. Obiekt nie powinien być widoczny dla innych wątków, ani nie należy odczytywać pólfinal
, dopóki wszystkie aktualizacje pólfinal
obiektu nie zostaną zakończone. Zamrożenie polafinal
następuje zarówno na końcu konstruktora, w którym ustawione jest polefinal
, jak i bezpośrednio po każdej modyfikacji polafinal
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 polufinal
nie mogą być obserwowane, ponieważ użycie tego polafinal
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 polafinal
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 ?
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
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
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.
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;
});
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 .
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.
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
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.
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