usar ejemplo codigo java reflection static private final

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 primitivos true y false en main están en modo automático para hacer referencia a Boolean "constantes" Boolean tipo Boolean.TRUE y Boolean.FALSE
  • La reflexión se utiliza para cambiar el public static final Boolean.FALSE para referirse al Boolean referido por Boolean.TRUE
  • Como resultado, posteriormente, cada vez que un false se realiza de forma automática a Boolean.FALSE , se refiere al mismo Boolean como el Boolean.TRUE por Boolean.TRUE
  • Todo lo que era "false" ahora es "true"

Preguntas relacionadas

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 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 los campos final deben leerse, hasta que todas las actualizaciones de los campos final del objeto estén completas. Las congelaciones de un campo final producen 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 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 campo final , ya que los usos de ese campo final 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 campos final . Dentro de un hilo, está permitido 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 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

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