Cambiar el campo final estático privado utilizando la reflexión de Java

Tengo una clase con un campo private static final que, lamentablemente, 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 forma de cambiar el valor?

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

Suponiendo que ningún SecurityManager le impide hacer esto, puede usar setAccessible para moverse en private y restablecer el modificador para deshacerse de la final , y realmente 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 lanza SecurityException , el código anterior imprime "Everything is true" .

Lo que se hace aquí es lo siguiente:

  • Los valores boolean primitivos true y false en main son autoboxed para referenciar el tipo Boolean “constants” Boolean.TRUE y Boolean.FALSE
  • La reflexión se usa para cambiar el public static final Boolean.FALSE para hacer referencia al Boolean refiere Boolean.TRUE
  • Como resultado, cada vez que un false se autoboxea en Boolean.FALSE , se refiere al mismo Boolean que el Boolean.TRUE por Boolean.TRUE
  • Todo lo que era "false" ahora es "true"

Preguntas relacionadas

  • Usar la reflexión para cambiar static final File.separatorChar para pruebas unitarias
  • ¿Cómo limitar setAccessible a solo usos “legítimos”?
    • Tiene ejemplos de jugar con la memoria caché de Integer , mutar una String , etc.

Advertencias

Se debe tener extremo cuidado cada vez que haga algo como esto. Puede que no funcione porque un SecurityManager puede estar presente, pero incluso si no lo hace, según el patrón de uso, puede funcionar o no.

JLS 17.5.3 Modificación posterior de los campos finales

En algunos casos, como la deserialización, el sistema deberá cambiar los campos final de un objeto después de la construcción. final campos final 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 campos final del objeto. El objeto no debe hacerse visible a otros hilos, ni deben leerse los campos final , hasta que se completen todas las actualizaciones de los campos final del objeto. Las congelaciones de un campo final ocurren tanto al final del constructor en el que se establece el campo final , como inmediatamente después de cada modificación de un campo final mediante reflexión u otro mecanismo especial.

Incluso entonces, hay una serie de complicaciones. Si se inicializa un campo final a una constante de tiempo de comstackción en la statement de campo, es posible que no se observen los cambios en el campo final , ya que los usos de ese campo final se reemplazan en tiempo de comstackción con la constante de tiempo de comstackción.

Otro problema es que la especificación permite una optimización agresiva de final campos final . Dentro de un hilo, es permisible reordenar las lecturas de un campo final con aquellas modificaciones de un campo final que no tienen lugar en el constructor.

Ver también

  • JLS 15.28 Expresión constante
    • Es poco probable que esta técnica funcione con un private static final boolean primitivo, porque es inlineable como una constante de tiempo de comstackción y, por lo tanto, el valor “nuevo” puede no ser observable

Apéndice: sobre la manipulación bit a bit

Esencialmente,

 field.getModifiers() & ~Modifier.FINAL 

apaga el bit correspondiente a Modifier.FINAL desde field.getModifiers() . & es el bitwise-y, y ~ es el complemento bit a bit.

Ver también

  • Wikipedia / operación Bitwise

Recordar expresiones constantes

¿Todavía no eres capaz de resolver esto ?, ¿has caído en la depresión como lo hice por ello? ¿Tu código se ve así?

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

Al leer 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 siento!

Para obtener más detalles acerca de por qué este comportamiento lee esto ?

Si el valor asignado a un campo static final boolean se conoce en tiempo de comstackción, es una constante. Los campos de tipo primitivo o String pueden ser constantes de tiempo de comstackción. Una constante se insertará en cualquier código que haga referencia al campo. Como el campo no se lee realmente en el 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), eliminar la palabra clave final o cambiar su valor no romperá la compatibilidad con los archivos 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 recomstackdos. Esto es cierto incluso si el uso en sí mismo no es una expresión constante en tiempo de comstackció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 descomstacks a Checker , verás que en lugar de hacer referencia a Flag.FLAG , el código simplemente empuja un valor de 1 ( true ) en la stack (instrucción 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 

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, System.out y System.err son campos finales estáticos que, por razones heredadas, deben poder cambiarse con 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

También lo integé con la biblioteca joor

Solo usa

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

También arreglé un problema con la override que las soluciones anteriores parecen perderse. Sin embargo, use esto con mucho cuidado, solo cuando no haya otra solución buena.

En caso de presencia de un administrador de seguridad, se puede hacer uso de AccessController.doPrivileged

Tomando el mismo ejemplo de la respuesta aceptada anteriormente:

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

Junto con la respuesta mejor clasificada, puede usar 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 del campo de destino y el indicador de forzado de accesibilidad (si juega con campos no públicos). Más información que puedes encontrar aquí .

La respuesta aceptada funcionó para mí hasta que se implementó en JDK 1.8u91. Luego me di cuenta de que fallaba en field.set(null, newValue); línea cuando había leído el valor por reflexión antes de llamar setFinalStatic método setFinalStatic .

Probablemente la lectura causó de alguna manera una configuración diferente de las sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl internas de la reflexión de Java (es decir, sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl en caso de sun.reflect.UnsafeStaticObjectFieldAccessorImpl en lugar de sun.reflect.UnsafeStaticObjectFieldAccessorImpl en caso de éxito) pero no lo sun.reflect.UnsafeStaticObjectFieldAccessorImpl más.

Como necesitaba establecer temporalmente un nuevo valor basado en el valor anterior y luego restablecer el valor anterior, cambié la firma poco para proporcionar la función de cálculo externamente y también devolver el valor anterior:

 public static  T assignFinalField(Object object, Class clazz, String fieldName, UnaryOperator 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.

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, así 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(); } } 

Una clase simple con la variable de cadena final. Entonces 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); } } 

El resultado será el 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

El objective de un campo final es que no se puede reasignar una vez establecido. La JVM usa esta garantía para mantener la coherencia en varios lugares (p. Ej., Clases internas que hacen referencia a variables externas). Entonces no ¡Ser capaz de hacerlo rompería la JVM!

La solución no es declararla final en primer lugar.