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

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



답변

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

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

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


답변

값이 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 언어 사양 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


답변

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

그냥 사용

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

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


답변

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


답변

보안 관리자가있는 경우 사용할 수 있습니다 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;
});


답변

허용 된 답변은 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;
    } ...

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