fix*_*ain 454 java reflection static final private
我有一个带有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);
Run Code Online (Sandbox Code Playgroud)
pol*_*nts 851
假设没有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"
}
}
Run Code Online (Sandbox Code Playgroud)
假设没有SecurityException抛出,上面的代码打印出来"Everything is true".
这里实际做的是如下:
boolean值true和falsein main被自动装箱以引用类型Boolean"常量" Boolean.TRUE和Boolean.FALSEpublic static final Boolean.FALSE引用所Boolean引用的Boolean.TRUEfalse被自动装箱时Boolean.FALSE,它指的是与Boolean所提到的相同Boolean.TRUE"false"现在的一切都是"true"static final File.separatorChar单元测试Integer缓存,变异String等的例子每当你做这样的事情时,都应该格外小心.它可能不起作用,因为SecurityManager可能存在,但即使它不存在,取决于使用模式,它可能或可能不起作用.
在某些情况下,例如反序列化,系统将需要
final在构造之后更改对象的字段.final可以通过反射和其他依赖于实现的方式来改变字段.它具有合理语义的唯一模式是构造对象然后final更新对象字段的模式.对象不应该对其他线程可见,也不应该final读取字段,直到final完成对象字段的所有更新.final字段的冻结既发生在final设置字段的构造函数的末尾,也发生在final通过反射或其他特殊机制对字段进行每次修改之后.即使这样,也有许多并发症.如果
final字段在字段声明中初始化为编译时常量,则final可能无法观察到对字段的更改,因为final在编译时将该字段的使用替换为编译时常量.另一个问题是规范允许积极优化
final字段.在一个线程中,允许final使用构造函数中不发生的最终字段的那些修改来重新排序字段的读取.
private static final boolean,因为它可以作为编译时常量内联,因此"新"值可能无法被观察到实质上,
field.getModifiers() & ~Modifier.FINAL
Run Code Online (Sandbox Code Playgroud)
关断对应于比特Modifier.FINAL从field.getModifiers().&是按位 - 和,~是按位补码.
仍然无法解决这个问题?已经像我一样陷入了萧条?你的代码看起来像这样吗?
public class A {
private final String myVar = "Some Value";
}
Run Code Online (Sandbox Code Playgroud)
阅读这个答案的评论,特别是@Pshemo的评论,它提醒我,Constant表达式处理不同,所以不可能修改它.因此,您需要将代码更改为如下所示:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
Run Code Online (Sandbox Code Playgroud)
如果你不是班上的老板......我觉得你!
有关此行为为什么读取此内容的详细信息?
eri*_*son 54
如果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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您反编译Checker,您将看到Flag.FLAG代码只是将1(true)的值推送到堆栈(指令#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
Run Code Online (Sandbox Code Playgroud)
小智 13
从Java语言规范第17章第17.5.4节"写保护字段"中获得一点好奇心:
通常,可能不会修改最终和静态字段.但是,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);
Run Code Online (Sandbox Code Playgroud)
我还override解决了以前的解决方案似乎错过的问题.但是,只有在没有其他好的解决方案时才非常小心地使用它.
如果存在安全管理器,则可以利用 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"
}
}
Run Code Online (Sandbox Code Playgroud)
在lambda表达式中AccessController.doPrivileged,可以简化为:
AccessController.doPrivileged((PrivilegedAction) () -> {
modifiersField.setAccessible(true);
return null;
});
Run Code Online (Sandbox Code Playgroud)
即使final一个字段可以在静态初始值设定项之外修改,并且(至少 JVM HotSpot)将完美地执行字节码。
问题是 Java 编译器不允许这样做,但是可以使用objectweb.asm. 这是 p?e?r?f?e?c?t?l?y? ?有效的??c?l?a?s?s?f?i?l?e? 从 JVMS 规范的角度来看,一个无效的类文件,但它通过了字节码验证,然后在 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();
Run Code Online (Sandbox Code Playgroud)
在Java中,类看起来大致如下:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
Run Code Online (Sandbox Code Playgroud)
不能用 编译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);
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
JVM HotSpot 检查的唯一限制是final不应在final声明该字段的类之外修改该字段。
从Java 12开始,给出的答案将不起作用。
这是一个关于如何private static final从Java 12开始修改字段的示例(基于此答案)。
private Object modifyField(Object newFieldValue, String fieldName, Object classInstance) throws NoSuchFieldException, IllegalAccessException {
Field field = classInstance.getClass().getDeclaredField(fieldName);
VarHandle MODIFIERS;
field.setAccessible(true);
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
Object previousValue = field.get(classInstance);
field.set(null, newFieldValue);
return previousValue;
}
Run Code Online (Sandbox Code Playgroud)
请参阅此线程了解更多详细信息。
| 归档时间: |
|
| 查看次数: |
220577 次 |
| 最近记录: |