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);
Author: smholloway, 2010-07-21

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 y false en main se autoboxean al tipo de referencia Boolean "constantes" Boolean.TRUE y Boolean.FALSE
  • La reflexión se utiliza para cambiar la public static final Boolean.FALSE para referirse al Boolean mencionado por Boolean.TRUE
  • Como resultado, posteriormente cada vez que un false se autoboxea a Boolean.FALSE, se refiere al mismo Boolean que el referido por Boolean.TRUE
  • Todo lo que era "false" ahora es "true"

Cuestiones conexas


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 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. Los bloqueos 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 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 campo final pueden no observarse, 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 los campos final. Dentro de un hilo, es permisible reordenar lecturas de un campo final 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

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?

 742
Author: polygenelubricants,
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
 46
Author: erickson,
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

 12
Author: Stephan Markwalder,
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.

 4
Author: iirekm,
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;
});
 4
Author: VanagaS,
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í.

 4
Author: nndru,
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.

 2
Author: Tomáš Záluský,
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

 -1
Author: hasskell,
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.

 -5
Author: thecoop,
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