ejemplo - Cambiar el campo final estático privado utilizando la reflexión de Java
jlabel java ejemplo (9)
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, de modo que con lo que me convertí:
/**
* @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 variable final de cadena. Así que en la clase principal importa 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);
}
}
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
De acuerdo con la documentación https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
Tengo una clase con un campo private static final
que, desafortunadamente, necesito cambiar en tiempo de ejecución.
Usando la reflexión obtengo este error: java.lang.IllegalAccessException: Can not set static final boolean field
¿Hay alguna manera de cambiar el valor?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
El punto principal de un campo final
es que no se puede reasignar una vez establecido. La JVM utiliza esta garantía para mantener la coherencia 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.
En caso de presencia de un administrador de seguridad, se 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 para:
AccessController.doPrivileged((PrivilegedAction) () -> {
modifiersField.setAccessible(true);
return null;
});
Junto con la mejor respuesta clasificada, puede utilizar un enfoque un poco más simple. FieldUtils
clase FieldUtils
Apache commons 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 de campo de destino y el indicador de forzado de accesibilidad (si juega con campos no públicos). Más información se puede encontrar here .
La respuesta aceptada funcionó para mí hasta que se implementó en JDK 1.8u91. Entonces me di cuenta de que había fallado en field.set(null, newValue);
línea cuando había leído el valor a través de la reflexión antes de llamar setFinalStatic
método setFinalStatic
.
Probablemente la lectura causó de alguna manera una configuración diferente de los elementos internos de la reflexión de Java (a saber, sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
en caso de sun.reflect.UnsafeStaticObjectFieldAccessorImpl
en lugar de sun.reflect.UnsafeStaticObjectFieldAccessorImpl
en caso de éxito) pero no sun.reflect.UnsafeStaticObjectFieldAccessorImpl
más detalles.
Ya que necesitaba establecer temporalmente un nuevo valor basado en el valor antiguo y luego restablecer el valor anterior, cambié la firma un 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.
Si el valor asignado a un campo static final boolean
se conoce en tiempo de compilación, es una constante. Los campos de tipo primitivo o String
pueden ser constantes en tiempo de compilación. Se insertará una constante 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á efecto.
La especificación del lenguaje Java dice esto:
Si un campo es una variable constante (§4.12.4), entonces eliminar la palabra clave final o cambiar su valor no interrumpirá la compatibilidad con los binarios preexistentes al hacer que no se ejecuten, pero no verán ningún valor nuevo para el uso del campo a menos que sean recompilados. Esto es cierto incluso si el uso en sí no es una expresión constante en tiempo de compilación (§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 descompila Checker
, verá que, en lugar de hacer referencia a Flag.FLAG
, el código simplemente empuja un valor de 1 ( true
) a 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
Suponiendo que ningún SecurityManager
le impida hacer esto, puede usar setAccessible
para moverse de forma private
y restablecer el modificador para deshacerse del final
, y 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 emita una excepción de SecurityException
, el código anterior se imprime "Everything is true"
.
Lo que realmente se hace aquí es el siguiente:
- Los valores
boolean
primitivostrue
yfalse
enmain
están en modo automático para hacer referencia aBoolean
"constantes"Boolean
tipoBoolean.TRUE
yBoolean.FALSE
- La reflexión se utiliza para cambiar el
public static final Boolean.FALSE
para referirse alBoolean
referido porBoolean.TRUE
- Como resultado, posteriormente, cada vez que un
false
se realiza de forma automática aBoolean.FALSE
, se refiere al mismoBoolean
como elBoolean.TRUE
porBoolean.TRUE
- Todo lo que era
"false"
ahora es"true"
Preguntas relacionadas
- Usando la reflexión para cambiar
static final File.separatorChar
para pruebas de unidad - ¿Cómo limitar setAccessible a solo usos "legítimos"?
- Tiene ejemplos de problemas con el caché de
Integer
, la mutación de unaString
, etc.
- Tiene ejemplos de problemas con el caché de
Advertencias
Se debe tener mucho cuidado cada vez que hagas algo como esto. Puede que no funcione porque puede estar presente un SecurityManager
, pero incluso si no lo hace, dependiendo del patrón de uso, puede o no funcionar.
JLS 17.5.3 Modificación posterior de los campos finales
En algunos casos, como la deserialización, el sistema necesitará cambiar los campos
final
de un objeto después de la construcción.final
camposfinal
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 aquel 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 los camposfinal
deben leerse, hasta que todas las actualizaciones de los camposfinal
del objeto estén completas. Las congelaciones de un campofinal
producen tanto al final del constructor en el que se establece el campofinal
, como inmediatamente después de cada modificación de un campofinal
través de la 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, es posible que no se observen cambios en el campofinal
, 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
final
camposfinal
. Dentro de un hilo, está permitido reordenar las lecturas de un campofinal
con aquellas modificaciones de un campo final que no tienen lugar en el constructor.
Ver también
- JLS 15.28 Expresión Constante
- Es improbable que esta técnica funcione con un
private static final boolean
primitivo, ya que es inlineable como una constante de compilación y, por lo tanto, el valor "nuevo" puede no ser observable
- Es improbable que esta técnica funcione con un
Apéndice: Sobre la manipulación bit a bit.
Esencialmente,
field.getModifiers() & ~Modifier.FINAL
desactiva el bit correspondiente a Modifier.FINAL
de field.getModifiers()
. &
es el bitwise y, y ~
es el complemento bitwise.
Ver también
Recuerda expresiones constantes
¿Aún no pudiendo resolver esto, he caído en la depresión como lo hice por ella? ¿Su código se ve así?
public class A {
private final String myVar = "Some Value";
}
Al leer los comentarios sobre esta respuesta, especialmente el 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 verse así:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
Si no eres el dueño de la clase ... ¡Te siento!
Para más detalles sobre por qué este comportamiento lee esto ?
También lo integré con la librería joor.
Solo usa
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
También solucioné un problema con la override
que las soluciones anteriores parecen fallar. Sin embargo, use esto con mucho cuidado, solo cuando no haya otra buena solución.
Un poco de 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, System.out y System.err son campos finales estáticos que, por razones heredadas, deben ser modificados por los métodos System.setIn, System.setOut y System.setErr. Nos referimos a estos campos como protegidos contra escritura para distinguirlos de los campos finales ordinarios.
Fuente: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4