Java 리플렉션을 사용하여 개인 정적 최종 필드 변경


479

private static final불행히도 런타임에 변경 해야하는 필드 가있는 클래스가 있습니다.

리플렉션을 사용하면이 오류가 발생합니다. java.lang.IllegalAccessException: Can not set static final boolean field

값을 변경하는 방법이 있습니까?

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

4
그런 나쁜 생각입니다. 소스를 가져 와서 다시 컴파일 (또는 디 컴파일 / 다시 컴파일)하려고합니다.
Bill K

System.out은 공개 정적 최종 필드이지만 변경할 수도 있습니다.
신뢰할 수없는

19
@irreputable System.out/in/err은 "특별"하므로 Java 메모리 모델에서 특별히 언급해야합니다. 그들은 따라야 할 예가 아닙니다.
Tom Hawtin-tackline

8
글쎄, 내 포인트 ws 사이에 해킹을 찾아 내 책임이 lib 책임이 다음 릴리스에서 변경을 할 때까지 더 이상 해킹 할 필요가 없을 때까지 ...
fixitagain

1
@Bill K : 10 년 전 : 다시 컴파일하는 것이 좋지만 배포 된 시스템에 있으며 배포 된 앱을 업데이트 할 수있을 때까지 패치 만하면됩니다!
Bill K

답변:


888

no SecurityManager로 인해이 작업을 수행 할 수 없다고 가정하면 수정자를 setAccessible해결 private하고 재설정하여 final실제로 private static final필드를 수정하는 데 사용할 수 있습니다.

예를 들면 다음과 같습니다.

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

아니오를 가정하면 SecurityException위의 코드가 인쇄 "Everything is true"됩니다.

실제로 여기서 수행되는 작업은 다음과 같습니다.

  • 프리미티브 booleantruefalse의가 main참조 형식으로되어 오토 박싱 Boolean"정수" Boolean.TRUEBoolean.FALSE
  • 리플렉션은 public static final Boolean.FALSE참조하는 참조 를 변경하는 데 사용 Boolean됩니다.Boolean.TRUE
  • 결과적으로 이후 falseBoolean.FALSE , 동일한 지칭Boolean 하게 참조됩니다.Boolean.TRUE
  • "false"지금은 모든 것이"true"

관련 질문


경고

이와 같은 일을 할 때마다 매우주의해야합니다. SecurityManager존재 하기 때문에 작동하지 않을 수 있지만 사용 패턴에 따라 작동하지 않거나 작동하지 않을 수 있습니다.

JLS 17.5.3 최종 필드의 후속 수정

역 직렬화와 같은 일부 경우 시스템은 final시공 후 객체 의 필드 를 변경해야합니다 . final필드는 리플렉션 및 기타 구현 종속 수단을 통해 변경 될 수 있습니다. 이것이 합리적인 의미론을 갖는 유일한 패턴은 객체가 구성된 다음 final객체 의 필드가 업데이트되는 패턴입니다. 객체 필드에 대한 final모든 업데이트 final가 완료 될 때까지 객체를 다른 스레드에 표시하거나 필드를 읽지 않아야합니다 . final필드 고정은 final필드가 설정된 생성자의 끝 과 final리플렉션 또는 기타 특수 메커니즘을 통해 필드가 수정 될 때마다 발생 합니다.

그럼에도 불구하고 많은 합병증이 있습니다. 경우 final필드가 필드 선언에서 컴파일 타임 상수로 초기화되면,로 변경finalfinal 로 컴파일 시간에 대체 되므로 필드 이 관찰되지 않을 수 있습니다 .

또 다른 문제는 사양이 final필드를 적극적으로 최적화 할 수 있다는 것 입니다. 스레드 내 final에서 생성자에서 발생하지 않는 최종 필드를 수정 하여 필드 읽기를 재정렬 할 수 있습니다.

또한보십시오

  • JLS 15.28 상수 표현
    • private static final boolean컴파일 타임 상수로 인라인 할 수 없으므로 "새로운"값을 관찰 할 수 없기 때문에이 기술이 프리미티브와 함께 작동 하지는 않습니다.

부록 : 비트 조작

본질적으로

field.getModifiers() & ~Modifier.FINAL

Modifier.FINALfrom에 해당하는 비트를 끕니다 field.getModifiers(). &비트 단위이며~ 비트 단위입니다.

또한보십시오


상수 표현식 기억

아직도이 문제를 해결할 수 없습니까? 제가했던 것처럼 우울증에 빠졌습니까? 코드가 다음과 같이 보입니까?

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

이 답변, @Pshemo에 의해 특별히 하나에 주석을 읽고, 그 생각 나게 상수 표현식 이 될 수 있도록 서로 다른 처리 불가능 을 수정할 수 있습니다. 따라서 다음과 같이 코드를 변경해야합니다.

public class A {
    private final String myVar;

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

당신이 수업의 주인이 아니라면 ...

이 동작은 이유에 대한 자세한 내용은 이 글을 읽을 ?


41
@thecoop, @HalfBrian : 이것이 EXTREMELY EVIL 이라는 것은 의심의 여지가 없지만이 예제는 설계에 의해 선택되었습니다. 내 대답은 일부 상황에서 이것이 어떻게 가능한지 보여줍니다. 내가 생각할 수있는 가장 역겨운 예는 사람들이이 기술에 대한 사랑에 빠지지 않고 즉시 혐오감을 줄 것이라는 희망으로 고의적으로 선택된 것입니다.
유성 윤활제

59
야 임마. 나는 당신이 반사하는 것을 좋아한다고 들었습니다.
Matthew Flaschen

11
Boolean.FALSE는 private이 아닙니다. 이것이 "private final static"멤버와 실제로 작동합니까?
mgaert

15
@mgaert,하지만 목표 클래스 getDeclaredField()대신에 사용해야 합니다.getField()
eis

11
+1. 비슷한 것을 바꾸려고 시도하고 final String myConstant = "x";실패 할 사람들을 위해 : 컴파일 타임 상수는 컴파일러에 의해 인라인 System.out.println(myConstant);System.out.println("x");것이므로 컴파일러가 컴파일 타임에 상수의 값을 알고 있기 때문에 코드를 작성할 때 컴파일 될 것입니다. 이 문제를 없애려면 런타임에 상수를 초기화해야합니다 final String myConstant = new String("x");. 또한 final int myField = 11사용 과 같은 프리미티브의 경우 final int myField = new Integer(11);또는final Integer myField = 11;
Pshemo

58

값이 static final boolean필드에 이 컴파일 타임에 알려진 경우 상수입니다. 프리미티브 또는 String유형의 필드는 컴파일 타임 상수 일 수 있습니다. 필드를 참조하는 모든 코드에서 상수가 인라인됩니다. 필드는 실제로 런타임에 읽지 않으므로 필드를 변경해도 아무런 영향을 미치지 않습니다.

그만큼 Java 언어 사양 이 말한다 :

필드가 상수 변수 (§4.12.4) 인 경우 키워드 final을 삭제하거나 값을 변경해도 기존 바이너리가 실행되지 않아 기존 바이너리와의 호환성이 손상되지는 않지만 사용법에 대한 새로운 값은 표시되지 않습니다 다시 컴파일하지 않으면 필드의 사용법 자체가 컴파일 타임 상수 표현식이 아닌 경우에도 마찬가지입니다 (§15.28)

예를 들면 다음과 같습니다.

class Flag {
  static final boolean FLAG = true;
}

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

디 컴파일 Checker하면을 참조하는 대신 Flag.FLAG코드가 단순히 true스택 에 1 ( ) 값을 푸시 한다는 것을 알 수 있습니다 (지침 # 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

그것은 처음 생각했지만 런타임에 컴파일 된 Java를 기억했습니다. 비트를 재설정하면 단순히 상수 대신 변수로 다시 컴파일됩니다.
Bill K

4
@Bill K-아니오, 이것은 JIT 컴파일을 의미하지 않습니다. 종속 클래스 파일에는 실제로 인라인 된 값이 포함되며 독립 클래스에 대한 참조는 없습니다. 테스트하는 것은 매우 간단한 실험입니다. 예를 추가하겠습니다.
erickson

1
이것이 Boolean.false를 재정의하는 @polygenelubricants의 대답으로 어떻게 움직입니까?
Bill K

26
@Bill K-polygenlubricants의 대답에서 필드는 컴파일 시간 상수가 아닙니다. 그것은 public static final Boolean FALSE = new Boolean(false)아니다public static final boolean FALSE = false
erickson

17

Java 언어 사양 17 장 17.5.4 "쓰기 방지 필드"에 대한 약간의 호기심 :

일반적으로 final 필드와 static 필드는 수정되지 않을 수 있습니다. 그러나 System.in, System.out 및 System.err는 정적 최종 필드이며, 레거시 이유로 System.setIn, System.setOut 및 System.setErr 메소드로 변경할 수 있어야합니다. 이러한 필드는 일반 최종 필드와 구분하기 위해 쓰기 방지 된 것으로 간주합니다.

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


9

또한 joor 라이브러리 와 통합했습니다.

그냥 사용

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

또한 override이전 솔루션이 누락 된 문제를 수정했습니다 . 그러나 다른 좋은 해결책이 없을 때만 이것을 매우 신중하게 사용하십시오.


이 작업을 시도하면 (JDK12) "최종 ___ 필드를 설정할 수 없습니다"라는 예외가 발생합니다.
Aaron Iba

@AaronIba Java 12 이상에서는 더 이상 사용할 수 없습니다.
NateS

7

최고 순위의 답변과 함께 약간 간단한 접근 방식을 사용할 수 있습니다. Apache Commons FieldUtils클래스에는 이미 작업을 수행 할 수있는 특정 메소드가 있습니다. FieldUtils.removeFinalModifier방법을 살펴보십시오 . 대상 필드 인스턴스와 내게 필요한 옵션 강제 플래그를 지정해야합니다 (비공개 필드로 재생하는 경우). 자세한 내용은 여기를 참조하십시오 .


이것은 현재 허용되는 답변보다 훨씬 간단한 해결책입니다.
Bernie

4
그렇습니까? 하나의 방법을 복사하면 전체 라이브러리를 가져 오는 것보다 간단한 솔루션처럼 보입니다 (복사하려는 방법과 동일한 작업을 수행함).
eski

1
Java 12 이상에서는 작동하지 않습니다.java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR

6

보안 관리자가있는 경우 사용할 수 있습니다 AccessController.doPrivileged

위의 허용 된 답변에서 동일한 예를 보았습니다.

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

람다 식에서을 다음과 AccessController.doPrivileged같이 단순화 할 수 있습니다.

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});

1
이것은 또한 Java 12 이상에서는 작동하지 않는 것 같습니다.
dan46st

예 @ dan1st, 당신이 맞아요! 해결 방법은 다음을 확인하십시오 : stackoverflow.com/a/56043252/2546381
VanagaS

2

허용 된 답변은 JDK 1.8u91에 배포 될 때까지 효과적이었습니다. 그런 다음 field.set(null, newValue);호출하기 전에 리플렉션을 통해 값을 읽었을 때 라인 에서 실패했음을 깨달았습니다.setFinalStatic 메서드 .

아마도 읽기로 인해 Java 리플렉션 내부 설정이 다르게 설정 sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl되었습니다 (즉 , 실패하는 경우)sun.reflect.UnsafeStaticObjectFieldAccessorImpl 지만 , 성공 사례가 사례) 더 자세히 설명하지는 않았습니다.

이전 값을 기반으로 새 값을 임시로 설정하고 나중에 이전 값을 다시 설정해야했기 때문에 서명 기능을 약간 변경하여 외부에서 계산 기능을 제공하고 이전 값을 반환했습니다.

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

그러나 일반적인 경우에는 이것으로 충분하지 않습니다.


2

도이면서 final필드는 전혀 문제 바이트 코드를 실행 static 초기화의 외부 (적어도 JVM 핫스팟) 변형 될 수있다.

문제는 Java 컴파일러가 이것을 허용하지 않지만을 사용하여 쉽게 우회 할 수 있다는 것 objectweb.asm입니다. 다음은 바이트 코드 확인을 통과하고 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();

Java에서 클래스는 대략 다음과 같이 보입니다.

public class Cl{
    private static final int fld;

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

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

로 컴파일 할 수 없지만 javacJVM에서로드하고 실행할 수 있습니다.

JVM HotSpot은 이러한 "상수"가 지속적인 폴딩에 참여하지 못하도록 이러한 클래스를 특별히 처리합니다. 이 검사는 클래스 초기화바이트 코드 다시 작성 단계에서 수행됩니다 .

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

JVM HotSpot이 확인하는 유일한 제한 사항 finalfinal필드가 선언 된 클래스 외부 에서 필드를 수정해서는 안된다는 것 입니다.


0

가능한 경우 리플렉션이나 런타임에 최종 변수를 변경할 수있는 경우 인터뷰 질문 중 하나에서 해당 질문을 보았습니다. 정말 관심있어서 내가 함께한 것을 :

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

최종 문자열 변수가있는 간단한 클래스. 메인 클래스에서 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);
    }
}

출력은 다음과 같습니다.

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

설명서에 따르면 https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html


게시물을 보셨습니까 ?
Ravindra HV

이 질문은 static최종 필드 에 대해 묻기 때문에이 코드는 작동하지 않습니다. setAccessible(true)최종 인스턴스 필드 설정에만 작동합니다.
Radiodef

0

필드가 개인용 인 경우 다음을 수행 할 수 있습니다.

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

NoSuchFieldException 던지기 / 처리


-4

의 요점 final필드의 일단 설정 한 후에는 재 할당 할 수 없다는 것입니다. JVM은이 보증을 사용하여 다양한 위치 (예 : 외부 변수를 참조하는 내부 클래스)에서 일관성을 유지합니다. 그래서 아니야. 그렇게 할 수 있으면 JVM이 손상 될 수 있습니다!

해결책은 final처음에 그것을 선언하지 않습니다 .


4
또한 final다중 스레드 실행에서 특별한 역할을 final합니다. 값을 변경 하면 Java 메모리 모델도 손상됩니다.
Péter Török

1
그리고 선언되지 않은 필드는 선언 final되어서는 안됩니다 static.
Tom Hawtin-tackline

2
@Tom : 일반적으로 그것은 사실이지만 아마도 모든 정적 가변 변수를 금지하지는 않습니다 .
bcat

7
@Tom : 왜 싱글 톤이 악한 지 읽은 적이 있습니까? 나는했다! 이제 나는 그들이 자바에서만 악하다는 것을 알고 있습니다. 그리고 사용자 정의 클래스 로더의 가용성 때문에 만 가능합니다. 그리고이 모든 것을 알고 난 후에 사용자 정의 클래스 로더를 사용하지 않기 때문에 후회없이 싱글 톤을 사용합니다. 그리고 싱글 톤이 일류 언어 기능인 스칼라도 마찬가지입니다. 싱글 톤이 악하다는 것은 잘 알려진 거짓 신화 입니다.
Martin

3
@Martin 나는 당신의 의견이 오래되었다는 것을 알고 있으며 지금 귀하의 견해가 바뀌었을 것입니다. 코드 에 숨겨진 복잡성 을 추가 합니다. 또한 n 개의 싱글 톤도 먼저 구성해야 한다는 사실을 모르고 단위 테스트를 수행 할 수 없습니다 . 그들은 의존성 주입의 대립입니다. 숨겨진 복잡성 을 갖는 함정 이 싱글 톤의 편리함을 능가하지 않는다는 결정을 내릴 수도 있지만, 많은 팀이 정당한 이유 때문에 반대 입장을 취합니다.
호감
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.