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"
됩니다.
여기서 실제로 수행되는 작업은 다음과 같습니다.
- 프리미티브
boolean
값true
과false
의가main
참조 형식으로되어 오토 박싱Boolean
"정수"Boolean.TRUE
및Boolean.FALSE
- 반사를 변경하는 데 사용되는
public static final Boolean.FALSE
받는 참조Boolean
하여 언급Boolean.TRUE
- 결과적으로 이후에
false
에 자동 박스가 추가 될 때마다는 로Boolean.FALSE
참조되는 것과 동일Boolean
하게 참조됩니다.Boolean.TRUE
"false"
지금은 모든 것이"true"
관련 질문
- 리플렉션을 사용하여
static final File.separatorChar
단위 테스트 변경 - setAccessible을 "합법적 인"용도로만 제한하는 방법은 무엇입니까?
Integer
의 캐시 를 엉망으로 만들고, 돌연변이하는String
등 의 예가 있습니다.
경고
이와 같은 일을 할 때마다 매우주의해야합니다. SecurityManager
존재 하기 때문에 작동하지 않을 수 있지만 사용 패턴에 따라 작동하지 않거나 작동하지 않을 수 있습니다.
역 직렬화와 같은 일부 경우 시스템은
final
시공 후 객체 의 필드 를 변경해야합니다 .final
필드는 리플렉션 및 기타 구현 종속 수단을 통해 변경 될 수 있습니다. 이것이 합리적인 의미론을 갖는 유일한 패턴은 객체가 구성된 다음final
객체 의 필드가 업데이트되는 패턴입니다. 객체 필드에 대한final
모든 업데이트final
가 완료 될 때까지 객체를 다른 스레드에 표시하거나 필드를 읽지 않아야합니다 .final
필드 고정은final
필드가 설정된 생성자의 끝 과final
리플렉션 또는 기타 특수 메커니즘을 통해 필드가 수정 될 때마다 발생 합니다.그럼에도 불구하고 많은 합병증이 있습니다.
final
필드 선언에서 필드가 컴파일 타임 상수로 초기화 된 경우 해당 필드의 사용이 컴파일 타임 상수 로 컴파일 시간에 대체final
되므로 필드 변경 사항 이 관찰되지 않을 수 있습니다final
.또 다른 문제는 사양이
final
필드를 적극적으로 최적화 할 수 있다는 것 입니다. 스레드 내final
에서 생성자에서 발생하지 않는 최종 필드를 수정 하여 필드 읽기를 재정렬 할 수 있습니다.
또한보십시오
- JLS 15.28 상수 표현
private static final boolean
컴파일 타임 상수로 인라인 할 수 없으므로 "새로운"값을 관찰 할 수 없기 때문에이 기술이 프리미티브와 함께 작동 하지는 않습니다.
부록 : 비트 조작
본질적으로
field.getModifiers() & ~Modifier.FINAL
Modifier.FINAL
from에 해당하는 비트를 끕니다 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
보안 관리자가있는 경우 사용할 수 있습니다 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;
});
또한 joor 라이브러리 와 통합했습니다.
그냥 사용
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
또한 override
이전 솔루션이 누락 된 문제를 수정했습니다 . 그러나 다른 좋은 해결책이 없을 때만 이것을 신중하게 사용하십시오.
최고 순위의 답변과 함께 약간 간단한 접근 방식을 사용할 수 있습니다. Apache Commons FieldUtils
클래스에는 이미 작업을 수행 할 수있는 특정 메소드가 있습니다. FieldUtils.removeFinalModifier
방법을 살펴보십시오 . 대상 필드 인스턴스와 내게 필요한 옵션 강제 플래그를 지정해야합니다 (비공개 필드로 재생하는 경우). 자세한 내용은 여기를 참조하십시오 .
수락 된 답변은 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;
} ...
그러나 일반적인 경우에는 이것으로 충분하지 않습니다.
필드가 개인용 인 경우 다음을 수행 할 수 있습니다.
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
NoSuchFieldException 던지기 / 처리
도이면서 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;
}
}
로 컴파일 할 수 없지만 javac
JVM에서로드하고 실행할 수 있습니다.
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이 점검하는 유일한 제한 사항 final
은 final
필드가 선언 된 클래스 외부 에서 필드를 수정해서는 안된다는 것 입니다.
가능한 경우 리플렉션이나 런타임에 최종 변수를 변경할 수있는 경우 인터뷰 질문 중 하나에서 해당 질문을 보았습니다. 정말 관심있어서 내가 함께한 것이 :
/**
* @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
final
필드의 요점은 일단 설정 한 후에는 재 할당 할 수 없다는 것입니다. JVM은이 보증을 사용하여 다양한 위치 (예 : 외부 변수를 참조하는 내부 클래스)에서 일관성을 유지합니다. 그래서 안돼. 그렇게 할 수 있으면 JVM이 손상 될 수 있습니다!
해결책은 final
처음에 그것을 선언하지 않습니다 .
참고 URL : https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection
'Programing' 카테고리의 다른 글
주어진 두 날짜 사이의 일 수를 계산하는 방법은 무엇입니까? (0) | 2020.02.17 |
---|---|
메소드 이름과 줄 번호를 인쇄하고 조건부로 NSLog를 비활성화하는 방법은 무엇입니까? (0) | 2020.02.17 |
PHP의 어느 위치에서나 배열에 새 항목 삽입 (0) | 2020.02.17 |
JavaScript에서 문자열 보간을 어떻게 수행 할 수 있습니까? (0) | 2020.02.17 |
문자열에서 모든 문자를 바꾸는 방법? (0) | 2020.02.17 |