Alterar o campo final estático privado usando a reflexão Java

479

Eu tenho uma classe com um private static finalcampo que, infelizmente, preciso alterá-lo em tempo de execução.

Usando reflexão, recebo este erro: java.lang.IllegalAccessException: Can not set static final boolean field

Existe alguma maneira de alterar o valor?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
fixitagain
fonte
4
Que péssima ideia. Eu tentaria obter a fonte e recompilar (ou mesmo descompilar / recompilar).
Bill K
System.out é um campo final estático público, mas também pode ser alterado.
irreputável
19
@ irreputable System.out/in/errsão tão "especiais" que o Java Memory Model precisa fazer uma menção especial a eles. Eles não são exemplos que devem ser seguidos.
Tom Hawtin - tackline
8
bem meu ponto ws para encontrar um hack no entre ter o meu trabalho app até que a lib make responsável a mudança na próxima versão para que eu não precisa cortar mais ...
fixitagain
1
@ Bill K de dez anos atrás: seria ótimo recompilá-lo, mas ele está em um sistema implantado e eu só preciso corrigi-lo até que possamos atualizar o aplicativo implantado!
Bill K

Respostas:

888

Assumindo que não SecurityManagerestá impedindo você de fazer isso, você pode usar setAccessiblepara se locomover privatee redefinir o modificador para se livrar finale realmente modificar um private static finalcampo.

Aqui está um exemplo:

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"
   }
}

Supondo que não SecurityExceptionseja lançado, o código acima será impresso "Everything is true".

O que realmente é feito aqui é o seguinte:

  • Os booleanvalores primitivos truee falsein mainsão autoboxed para referenciar o tipo Boolean"constantes" Boolean.TRUEeBoolean.FALSE
  • A reflexão é usada para alterar a public static final Boolean.FALSEreferência ao Booleanreferido porBoolean.TRUE
  • Como resultado, posteriormente, sempre que a falseé autoboxed Boolean.FALSE, refere-se ao mesmo Booleanque o referido porBoolean.TRUE
  • Tudo o que era "false"agora é"true"

Perguntas relacionadas


Ressalvas

Cuidado extremo deve ser tomado sempre que você fizer algo assim. Pode não funcionar porque SecurityManagerpode estar presente, mas mesmo se não funcionar, dependendo do padrão de uso, pode ou não funcionar.

JLS 17.5.3 Modificação subsequente dos campos finais

Em alguns casos, como desserialização, o sistema precisará alterar os finalcampos de um objeto após a construção. finalos campos podem ser alterados via reflexão e outros meios dependentes da implementação. O único padrão no qual isso tem semântica razoável é aquele no qual um objeto é construído e, em seguida, os finalcampos do objeto são atualizados. O objeto não deve ficar visível para outros threads, nem os finalcampos devem ser lidos, até que todas as atualizações nos finalcampos do objeto estejam completas. Os congelamentos de um finalcampo ocorrem no final do construtor em que o finalcampo está definido e imediatamente após cada modificação de um finalcampo por meio de reflexão ou outro mecanismo especial.

Mesmo assim, existem várias complicações. Se um finalcampo for inicializado para uma constante em tempo de compilação na declaração de campo, as alterações no finalcampo poderão não ser observadas, pois os usos desse finalcampo serão substituídos no tempo de compilação pela constante em tempo de compilação.

Outro problema é que a especificação permite otimização agressiva dos finalcampos. Dentro de um encadeamento, é permitido reordenar leituras de um finalcampo com as modificações de um campo final que não ocorram no construtor.

Veja também

  • Expressão constante JLS 15.28
    • É improvável que essa técnica funcione com uma primitiva private static final boolean, porque é inlinável como uma constante em tempo de compilação e, portanto, o valor "novo" pode não ser observável

Apêndice: Na manipulação bit a bit

Essencialmente,

field.getModifiers() & ~Modifier.FINAL

desativa o bit correspondente a Modifier.FINALpartir de field.getModifiers(). &é o bit a bit - e, e ~é o bit a bit - complemento.

Veja também


Lembre-se de expressões constantes

Ainda não sendo capaz de resolver isso ?, caíram em depressão como eu fiz? Seu código se parece com isso?

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

Lendo os comentários sobre esta resposta, especialmente a de @Pshemo, lembrou-me que as Expressões Constantes são tratadas de maneira diferente, pelo que será impossível modificá-la. Portanto, você precisará alterar seu código para ficar assim:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

se você não é o dono da turma ... eu sinto você!

Para mais detalhes sobre por que esse comportamento foi lido ?

poligenelubricants
fonte
41
@ thecoop, @ HalfBrian: não há dúvida de que isso é EXTREMAMENTE MAL , mas esse exemplo foi escolhido por design. Minha resposta mostra apenas como, em algumas circunstâncias, isso é possível. O exemplo mais repugnante que consigo pensar é o escolhido deliberadamente, com a esperança de que talvez as pessoas fiquem repugnadas instantaneamente ao invés de se apaixonarem pela técnica.
polygenelubricants
59
Ei, nossa. Ouvi você gostar de reflexão, então refleti no campo para que você possa refletir enquanto reflete.
Matthew Flaschen 22/03
11
Observe que Boolean.FALSE não é privado. Isso realmente funciona com membros "estáticos finais estáticos"?
mgaert
15
@mgaert ele faz, mas você tem que usar getDeclaredField()em vez de getField()para a classe alvo
eis
11
+1. Para aqueles que tentarão mudar algo como final String myConstant = "x";e falharão: lembre-se de que constantes de tempo de compilação serão incorporadas pelo compilador; assim, quando você escrever um código como System.out.println(myConstant);ele será compilado, System.out.println("x");porque o compilador sabe o valor da constante no momento da compilação. Para se livrar desse problema, você precisa inicializar suas constantes no tempo de execução, como final String myConstant = new String("x");. Também no caso de primitivas como final int myField = 11uso final int myField = new Integer(11);oufinal Integer myField = 11;
Pshemo
58

Se o valor atribuído a um static final booleancampo for conhecido em tempo de compilação, será uma constante. Os campos primitivos ou de Stringtipo podem ser constantes em tempo de compilação. Uma constante será incorporada em qualquer código que faça referência ao campo. Como o campo não é realmente lido em tempo de execução, alterá-lo não terá efeito.

A especificação da linguagem Java diz o seguinte:

Se um campo for uma variável constante (§4.12.4), a exclusão da palavra-chave final ou a alteração de seu valor não quebrará a compatibilidade com os binários pré-existentes, fazendo com que eles não sejam executados, mas eles não verão nenhum novo valor para o uso do campo, a menos que sejam recompilados. Isso é verdade mesmo se o uso em si não for uma expressão constante em tempo de compilação (§15.28)

Aqui está um exemplo:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

Se você descompilar Checker, verá que, em vez de fazer referência Flag.FLAG, o código simplesmente coloca um valor de 1 ( true) na pilha (instrução # 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
erickson
fonte
Esse foi o meu primeiro pensamento, mas lembrei-me de Java compilado em tempo de execução; se você redefinisse o bit, ele seria recompilado como uma variável em vez de uma constante.
Bill K
4
@ Bill K - Não, isso não se refere à compilação JIT. Na verdade, os arquivos de classe dependentes conterão os valores incorporados e nenhuma referência à classe independente. É um experimento bem simples de testar; Vou adicionar um exemplo.
Erickson 21/07
1
Como isso se encaixa na resposta de @polygenelubricants, onde ele redefine o Boolean.false? - mas você está correto, eu já vi esse comportamento quando as coisas não foram recompiladas corretamente.
Bill K
26
@Bill K - na resposta dos poliglubrificantes, o campo não é uma constante de tempo de compilação. É public static final Boolean FALSE = new Boolean(false)nãopublic static final boolean FALSE = false
Erickson
17

Um pouco de curiosidade do Java Language Specification, capítulo 17, seção 17.5.4 "Campos protegidos contra gravação":

Normalmente, um campo final e estático não pode ser modificado. No entanto, System.in, System.out e System.err são campos finais estáticos que, por motivos herdados, devem ter permissão para serem alterados pelos métodos System.setIn, System.setOut e System.setErr. Nós nos referimos a esses campos como protegidos contra gravação para distingui-los dos campos finais comuns.

Fonte: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4

Stephan Markwalder
fonte
9

Também o integrei com a biblioteca joor

Apenas use

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

Também corrigi um problema com o overridequal as soluções anteriores parecem faltar. No entanto, use isso com muito cuidado, somente quando não houver outra boa solução.

iirekm
fonte
Quando tento isso (JDK12), recebo uma exceção: "Não é possível definir o campo ___ final".
Aaron Iba
@AaronIba Não é mais permitido no Java 12+.
NateS 18/02
7

Juntamente com a resposta mais bem classificada, você pode usar uma abordagem um pouco mais simples. A FieldUtilsclasse Apache commons já possui um método específico que pode fazer as coisas. Por favor, dê uma olhada no FieldUtils.removeFinalModifiermétodo. Você deve especificar a instância do campo de destino e o sinalizador forçador de acessibilidade (se você jogar com campos não públicos). Mais informações você encontra aqui .

nndru
fonte
Esta é uma solução muito mais simples do que a resposta atualmente aceita
Bernie
4
É isso? Copiar um método parece uma solução mais simples do que importar uma biblioteca inteira (que está fazendo a mesma coisa que o método que você está copiando).
Eski
1
Não funciona em Java 12+:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR
6

No caso de presença de um Security Manager, pode-se fazer uso de AccessController.doPrivileged

Tomando o mesmo exemplo da resposta aceita acima:

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"
    }
}

Na expressão lambda,, AccessController.doPrivilegedpode ser simplificado para:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
VanagaS
fonte
1
Isso também parece não funcionar com o java 12+.
dan1st 10/04
sim @ dan1st, você está certo! Verifique isso em busca de uma solução: stackoverflow.com/a/56043252/2546381
VanagaS
2

A resposta aceita funcionou para mim até a implantação no JDK 1.8u91. Então percebi que ele falhou na field.set(null, newValue);linha quando li o valor via reflexão antes de chamar o setFinalStaticmétodo.

Provavelmente, a leitura causou uma configuração diferente dos internos de reflexão do Java (ou seja, sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImplno caso de falha em vez de sun.reflect.UnsafeStaticObjectFieldAccessorImplno caso de sucesso), mas eu não o elaborei mais.

Como eu precisava definir temporariamente o novo valor com base no valor antigo e, posteriormente, retornar o valor antigo, alterei um pouco a assinatura para fornecer a função de computação externamente e também retornar o valor antigo:

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

No entanto, para casos gerais, isso não seria suficiente.

Tomáš Záluský
fonte
2

Mesmo sendo finalum campo, pode ser modificado fora do inicializador estático e (pelo menos JVM HotSpot) executará o bytecode perfeitamente.

O problema é que o compilador Java não permite isso, mas isso pode ser facilmente ignorado usando objectweb.asm. Aqui está um arquivo de classe perfeitamente válido que passa na verificação de código de código e é carregado e inicializado com êxito no JVM HotSpot OpenJDK12:

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

Em Java, a classe tem a seguinte aparência:

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

que não pode ser compilado javac, mas pode ser carregado e executado pela JVM.

O JVM HotSpot possui tratamento especial para essas classes, no sentido de impedir que essas "constantes" participem de dobras constantes. Essa verificação é feita na fase de reescrita de bytecode da inicialização da classe :

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

A única restrição que a JVM HotSpot verifica é que o finalcampo não deve ser modificado fora da classe em que o finalcampo está declarado.

Some Name
fonte
0

Só vi essa pergunta em uma das perguntas da entrevista, se possível alterar a variável final com reflexão ou em tempo de execução. Fiquei realmente interessado, para que eu me tornasse:

 /**
 * @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();
    }
}

Alguma classe simples com a variável String final. Portanto, na classe principal import 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);
    }
}

A saída será a seguinte:

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 acordo com a documentação https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html

hasskell
fonte
Você já viu este post?
Ravindra HV
Esta pergunta é sobre um staticcampo final, portanto esse código não funciona. setAccessible(true)funciona apenas para definir campos de instância final.
Radiodef
0

Se o seu campo é simplesmente privado, você pode fazer o seguinte:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

e throw / handle NoSuchFieldException

Philip Rego
fonte
-4

O ponto principal de um finalcampo é que ele não pode ser reatribuído depois de definido. A JVM usa essa garantia para manter a consistência em vários locais (por exemplo, classes internas que referenciam variáveis ​​externas). Então não. Ser capaz de fazer isso quebraria a JVM!

A solução é não declarar finalem primeiro lugar.

thecoop
fonte
4
Além disso, finaltem um papel especial na execução multithread - a alteração de finalvalores também quebraria o modelo de memória Java.
Péter Török
1
E um campo não declarado finalnão deve ser declarado static.
Tom Hawtin - defina
2
@ Tom: Em geral, isso provavelmente é verdade, mas eu não proibiria todas as variáveis ​​estáticas mutáveis.
BCAT
7
@ Tom: Você já leu por que singletons são maus? Eu fiz! Agora eu sei que eles fazem mal apenas em Java. E apenas devido à disponibilidade de um carregador de classes definido pelo usuário. E desde que eu sei tudo isso e não uso o carregador de classes definido pelo usuário, uso singletons sem arrependimento. E Scala, onde singleton é uma característica da linguagem de primeira classe - que singletons são maus é um mito falso bem conhecido .
Martin
3
@ Martin Eu sei que seu comentário é antigo, e talvez seus pontos de vista tenham mudado até agora, mas pensei em acrescentar: Singletons são maus por razões que não têm nada a ver com Java. Eles adicionam complexidade oculta ao seu código. Além disso, eles podem tornar impossível o teste de unidade sem também saber que n singletons também devem ser configurados primeiro. Eles são a antítese da injeção de dependência. Sua equipe pode tomar a decisão de que as armadilhas da complexidade oculta não superam a conveniência dos Singletons, mas muitas equipes adotam a posição oposta por um bom motivo.
esmague