Cambiar el campo final estático privado usando Java reflection
Tengo una clase con un campo private static final
que, desafortunadamente, necesito cambiar en tiempo de ejecución.
Usando reflexión obtengo este error: java.lang.IllegalAccessException: Can not set static final boolean field
¿hay alguna forma de cambiar el valor?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
9 answers
Asumiendo que no SecurityManager
le impide hacer esto, puede usar setAccessible
para moverse por private
y restablecer el modificador para deshacerse de final
, y en realidad modificar un campo private static final
.
Aquí hay un ejemplo:
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"
}
}
Suponiendo que no se arroje SecurityException
, el código anterior imprime "Everything is true"
.
Lo que realmente se hace aquí es lo siguiente: {[50]]}
- Los valores primitivos
boolean
true
yfalse
enmain
se autoboxean al tipo de referenciaBoolean
"constantes"Boolean.TRUE
yBoolean.FALSE
- La reflexión se utiliza para cambiar la
public static final Boolean.FALSE
para referirse alBoolean
mencionado porBoolean.TRUE
- Como resultado, posteriormente cada vez que un
false
se autoboxea aBoolean.FALSE
, se refiere al mismoBoolean
que el referido porBoolean.TRUE
- Todo lo que era
"false"
ahora es"true"
Cuestiones conexas
-
Uso de la reflexión para cambiar
static final File.separatorChar
para las pruebas unitarias -
Cómo limitar setAccessible a solo "legítimos" utiliza?
- Tiene ejemplos de jugar con la caché de
Integer
, mutando aString
, etc
- Tiene ejemplos de jugar con la caché de
Advertencias
Se debe tener mucho cuidado cada vez que hagas algo como esto. Puede que no funcione porque un SecurityManager
puede estar presente, pero incluso si no funciona, dependiendo del patrón de uso, puede o no funcionar.
JLS 17.5.3 Modificación posterior de los campos finales
En algunos casos, tales como deserialización, el sistema necesitará cambiar los campos
final
de un objeto después de la construcción.final
los campos se pueden cambiar a través de la reflexión y otros medios dependientes de la implementación. El único patrón en el que esto tiene una semántica razonable es uno en el que se construye un objeto y luego se actualizan los camposfinal
del objeto. El objeto no debe hacerse visible a otros hilos, ni deben leerse los camposfinal
, hasta que se completen todas las actualizaciones de los camposfinal
del objeto. Los bloqueos de un campofinal
ocurren tanto al final del constructor en el que se establece el campofinal
, como inmediatamente después de cada modificación de un campofinal
a través de reflexión u otro mecanismo especial.Incluso entonces, hay una serie de complicaciones. Si un campo
final
se inicializa a una constante de tiempo de compilación en la declaración de campo, los cambios en el campofinal
pueden no observarse, ya que los usos de ese campofinal
se reemplazan en tiempo de compilación con la constante de tiempo de compilación.Otro problema es que la especificación permite una optimización agresiva de los campos
final
. Dentro de un hilo, es permisible reordenar lecturas de un campofinal
con aquellas modificaciones de un campo final que no tienen lugar en el constructor.
Véase también
-
JLS 15.28 Expresión constante
- Es poco probable que esta técnica funcione con un primitivo
private static final boolean
, porque es inlineable como una constante de tiempo de compilación y por lo tanto el "nuevo" el valor puede no ser observable
- Es poco probable que esta técnica funcione con un primitivo
Apéndice: En la manipulación bitwise
Esencialmente,
field.getModifiers() & ~Modifier.FINAL
Apaga el bit correspondiente a Modifier.FINAL
de field.getModifiers()
. &
es el complemento bit a bit, y ~
es el complemento bit a bit.
Véase también
Recordar Expresiones Constantes
¿Todavía no puedes resolver esto? han caído en la depresión como yo ¿lo hizo por él? ¿Tu código se ve así?
public class A {
private final String myVar = "Some Value";
}
Leyendo los comentarios sobre esta respuesta, especialmente la de @Pshemo, me recordó que Las expresiones constantes se manejan de manera diferente, por lo que será imposible modificarlas. Por lo tanto, tendrá que cambiar su código para que se vea así:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
si no eres el dueño de la clase... ¡Te entiendo!
Para más detalles sobre por qué este comportamiento lea esto?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-06 06:27:57
Si el valor asignado a un campo static final boolean
es conocido en tiempo de compilación, es una constante . Campos de primitivo o
String
type puede ser constante en tiempo de compilación. Una constante se insertará en cualquier código que haga referencia al campo. Dado que el campo no se lee realmente en tiempo de ejecución, cambiarlo no tendrá ningún efecto.
La especificación del lenguaje Java dice esto:
Si un campo es una variable constante (§4.12.4), luego eliminar la palabra clave final o cambiar su valor no romper la compatibilidad con preexistentes binarios haciendo que no se ejecuten, pero no verán ningún valor nuevo para el uso del campo a menos que son recompilados. Esto es cierto incluso si el uso en sí no es un tiempo de compilación expresión constante (§15.28)
Aquí hay un ejemplo:
class Flag {
static final boolean FLAG = true;
}
class Checker {
public static void main(String... argv) {
System.out.println(Flag.FLAG);
}
}
Si descompilar Checker
, verás que en lugar de hacer referencia a Flag.FLAG
, el código, simplemente presiona un valor de 1 (true
) en la pila (instrucción #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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-05 17:32:08
Una pequeña curiosidad de la Especificación del lenguaje Java, capítulo 17, sección 17.5.4 "Campos protegidos contra escritura":
Normalmente, un campo que es final y estático no puede ser modificado. Sin embargo, System.in, Sistema.fuera, y el Sistema.err son campos finales estáticos que, por razones heredadas, debe permitirse ser cambiado por los métodos Sistema.setIn, Sistema.setOut y Sistema.setErr. Nos referimos a estos campos protegidos contra escritura para distinguirlos de los ordinarios final campo.
Fuente: 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-21 19:21:18
También lo integré con joor library
Solo use
Reflect.on(yourObject).setFinal("finalFieldName", finalFieldValue);
También he corregido un problema con override
que las soluciones anteriores parecen perder.
Sin embargo, utilice esto con mucho cuidado, solo cuando no hay otra buena solución.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-31 01:06:32
En caso de presencia de un Administrador de seguridad, uno puede hacer uso de AccessController.doPrivileged
Tomando el mismo ejemplo de la respuesta aceptada arriba:
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"
}
}
En la expresión lambda, AccessController.doPrivileged
, se puede simplificar a:
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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-25 06:06:43
Junto con la respuesta mejor clasificada, puede usar un enfoque un poco más simple. La clase Apache commons FieldUtils
ya tiene un método particular que puede hacer las cosas. Por favor, eche un vistazo al método FieldUtils.removeFinalModifier
. Debe especificar la instancia del campo de destino y el indicador de forzamiento de accesibilidad (si juega con campos no públicos). Más información puede encontrar aquí.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-07 10:32:39
La respuesta aceptada funcionó para mí hasta que se implementó en JDK 1.8u91.
Entonces me di cuenta de que falló en la línea field.set(null, newValue);
cuando había leído el valor a través de reflexión antes de llamar al método setFinalStatic
.
Probablemente la lectura causó de alguna manera una configuración diferente de los internos de reflexión de Java (a saber, sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
en caso de fallo en lugar de sun.reflect.UnsafeStaticObjectFieldAccessorImpl
en caso de éxito), pero no lo elaboré más.
Dado que necesitaba establecer temporalmente un nuevo valor basado en el valor anterior y luego volver a establecer el valor anterior, cambié la firma poco para proporcionar la función de cálculo externamente y también devolver el valor antiguo:
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;
} ...
Sin embargo, para el caso general esto no sería suficiente.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-10 12:45:19
Acabo de ver esa pregunta en una de las preguntas de la entrevista, si es posible cambiar la variable final con reflexión o en tiempo de ejecución. Me interesé mucho, por lo que me convertí en:
/**
* @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();
}
}
Alguna clase simple con la variable de cadena final. Así que en la clase principal importar java.lang.reflejar.Campo;
/**
* @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);
}
}
La salida será la siguiente:
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
Según la documentación 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-04 09:21:27
El punto de un campo final
es que no se puede reasignar una vez establecido. La JVM utiliza esta garantía para mantener la consistencia en varios lugares (por ejemplo, clases internas que hacen referencia a variables externas). Así que no. Ser capaz de hacerlo rompería la JVM!
La solución no es declararlo final
en primer lugar.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-21 16:38:55