Tengo una clase con un private static final
campo que, desafortunadamente, necesito cambiarlo en tiempo de ejecución.
Usando la reflexión me sale 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);
System.out/in/err
son tan "especiales" que el modelo de memoria de Java debe hacer una mención especial de ellos. No son ejemplos que deben seguirse.Respuestas:
Suponiendo que no
SecurityManager
te impide hacer esto, puedes usarlosetAccessible
para desplazarteprivate
y restablecer el modificador para deshacerte de élfinal
y, de hecho, modificar unprivate static final
campo.Aquí hay un ejemplo:
Asumiendo que no
SecurityException
se lanza, se imprime el código anterior"Everything is true"
.Lo que realmente se hace aquí es lo siguiente:
boolean
valores primitivostrue
yfalse
enmain
se autoboxing al tipo de referenciaBoolean
"constantes"Boolean.TRUE
yBoolean.FALSE
public static final Boolean.FALSE
referencia a laBoolean
referencia deBoolean.TRUE
false
se autoboxed aBoolean.FALSE
, se refiere a la mismaBoolean
como el referido a porBoolean.TRUE
"false"
ahora es"true"
Preguntas relacionadas
static final File.separatorChar
para pruebas unitariasInteger
el caché, mutar aString
, etc.Advertencias
Se debe tener mucho cuidado cuando haga algo como esto. Es posible que no funcione porque
SecurityManager
puede estar presente, pero incluso si no lo hace, dependiendo del patrón de uso, puede funcionar o no.Ver también
private static final boolean
, porque es inlineable como una constante de tiempo de compilación y, por lo tanto, el valor "nuevo" puede no ser observableApéndice: sobre la manipulación bit a bit
Esencialmente,
apaga el bit correspondiente a
Modifier.FINAL
fromfield.getModifiers()
.&
es el bit a bit y~
el complemento a bit.Ver también
Recuerda expresiones constantes
¿Aún no puedes resolver esto ?, ¿has caído en la depresión como lo hice yo? ¿Su código se ve así?
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 modificarla. Por lo tanto, deberá cambiar su código para que se vea así:
si no eres el dueño de la clase ... te siento!
Para más detalles sobre por qué este comportamiento lee esto ?
fuente
getDeclaredField()
en lugar degetField()
para la clase de objetivofinal String myConstant = "x";
y fallarán: recuerden que las constantes de tiempo de compilación serán alineadas por el compilador, de modo que cuando escriban código comoSystem.out.println(myConstant);
si fuera compilado comoSystem.out.println("x");
porque el compilador conoce el valor de la constante en el momento de la compilación. Para deshacerse de este problema, debe inicializar sus constantes en tiempo de ejecución comofinal String myConstant = new String("x");
. También en caso de primitivas como elfinal int myField = 11
usofinal int myField = new Integer(11);
ofinal Integer myField = 11;
Si el valor asignado a un
static final boolean
campo se conoce en tiempo de compilación, es una constante. Los campos de primitivo oString
tipo pueden ser constantes de tiempo de compilación. Se incluirá una constante en cualquier código que haga referencia al campo. Como 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:
Aquí hay un ejemplo:
Si descompila
Checker
, verá que en lugar de hacer referenciaFlag.FLAG
, el código simplemente empuja un valor de 1 (true
) en la pila (instrucción # 3).fuente
public static final Boolean FALSE = new Boolean(false)
nopublic static final boolean FALSE = false
Un poco de curiosidad de la Especificación del lenguaje Java, capítulo 17, sección 17.5.4 "Campos protegidos contra escritura":
Fuente: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
fuente
También lo integré con joor library
Solo usa
También solucioné un problema con el
override
que las soluciones anteriores parecen fallar. Sin embargo, use esto con mucho cuidado, solo cuando no haya otra buena solución.fuente
Junto con la respuesta mejor clasificada, puede utilizar un enfoque un poco más simple. La
FieldUtils
clase commons de Apache ya tiene un método particular que puede hacer las cosas. Por favor, eche un vistazo alFieldUtils.removeFinalModifier
método. Debe especificar la instancia de campo de destino y la marca de forzamiento de accesibilidad (si juega con campos no públicos). Más información puedes encontrar aquí .fuente
java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
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:
En la expresión lambda
AccessController.doPrivileged
, se puede simplificar a:fuente
La respuesta aceptada funcionó para mí hasta que se implementó en JDK 1.8u91. Luego me di cuenta de que fallaba en la
field.set(null, newValue);
línea cuando había leído el valor mediante reflexión antes de llamar alsetFinalStatic
método.Probablemente la lectura causó de alguna manera una configuración diferente de los elementos internos de reflexión de Java (es decir,
sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
en caso de error en lugar desun.reflect.UnsafeStaticObjectFieldAccessorImpl
en caso de éxito), pero no lo desarrollé más.Como necesitaba establecer temporalmente un nuevo valor basado en el valor anterior y luego volver a establecer el valor anterior, cambié un poco la firma para proporcionar externamente la función de cálculo y también devolver el valor anterior:
Sin embargo, para un caso general, esto no sería suficiente.
fuente
Incluso a pesar de ser
final
un campo puede modificarse fuera del inicializador estático y (al menos JVM HotSpot) ejecutará el código de bytes perfectamente bien.El problema es que el compilador de Java no permite esto, pero esto puede pasarse por alto fácilmente
objectweb.asm
. Aquí hay un archivo de clase perfectamente válido que pasa la verificación de bytecode y se carga e inicializa con éxito en JVM HotSpot OpenJDK12:En Java, la clase se ve más o menos como sigue:
que no se puede compilar con
javac
, pero JVM puede cargar y ejecutar.JVM HotSpot tiene un tratamiento especial de tales clases en el sentido de que evita que tales "constantes" participen en el plegado constante. Esta comprobación se realiza en la fase de reescritura de bytecode de inicialización de clase :
La única restricción que comprueba JVM HotSpot es que el
final
campo no debe modificarse fuera de la clase en la quefinal
se declara el campo.fuente
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í:
Alguna clase simple con la variable de cadena final. Entonces, en la clase principal import java.lang.reflect.Field;
El resultado será el siguiente:
De acuerdo con la documentación https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
fuente
static
campo final, por lo que este código no funciona.setAccessible(true)
solo funciona para establecer campos de instancia final.Si su campo es simplemente privado, puede hacer esto:
y lanzar / manejar NoSuchFieldException
fuente
Todo el punto de un
final
campo 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). Entonces no. ¡Ser capaz de hacerlo rompería la JVM!La solución es no declararlo
final
en primer lugar.fuente
final
tiene un papel especial en la ejecución multiproceso: cambiar losfinal
valores también rompería el modelo de memoria Java.final
no debe ser declaradostatic
.