需要通过反射的方式修改某个类的private static final
字段。
// java.lang.reflect.Field
public class FinalFieldModify {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
System.out.println("Before modify: contains c = " + DataHolder.contains("c"));
Field field = DataHolder.class.getDeclaredField("data");
field.setAccessible(true);
List<String> origin = (List<String>) field.get(null);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 由于 List.of 返回的是不可变 List,
// 所以 只能新建一个 List,增加元素,再替换原来的 List
ArrayList<String> cur = new ArrayList<>(origin.size() + 1);
cur.addAll(origin);
cur.add("c");
field.set(null, cur);
System.out.println("After modify: contains c = " + DataHolder.contains("c"));
}
private static class DataHolder {
private static final List<String> data = List.of("a", "b");
private static boolean contains(String s) {
return data.contains(s);
}
}
}
运行上述代码后会抛出如下异常:Can not set static final java.util.List field 。
由于已经通过反射把字段的private
和final
两个修饰符都去掉了,所以很诡异。
只需要把上面的第 8 行挪到第 13 行就行。即在对字段的修饰符通过反射修改之前,不能通过反射获取该字段的值。
... 略
field.setAccessible(true);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
List<String> origin = (List<String>) field.get(null);
// 由于 List.of 返回的是不可变 List,
// 所以 只能新建一个 List,增加元素,再替换原来的 List
ArrayList<String> cur = new ArrayList<>(origin.size() + 1);
输出结果:
Before modify: contains c = false
After modify: contains c = true
通过上面的解决方案可以看出,关键问题在于通过反射获取字段的值这个操作。
{% code title="" %}
public final class Field extends AccessibleObject implements Member
{
// Cached field accessor created with override
private FieldAccessor overrideFieldAccessor;
public Object get(Object obj) {
return getFieldAccessor(obj).get(obj);
}
public void set(Object obj, Object value) {
getFieldAccessor(obj).set(obj, value);
}
private FieldAccessor getFieldAccessor(Object obj) {
FieldAccessor a = overrideFieldAccessor;
return (a != null) ? a : acquireFieldAccessor(true);
}
private FieldAccessor acquireFieldAccessor(boolean overrideFinalCheck) {
FieldAccessor tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
setFieldAccessor(tmp, overrideFinalCheck);
return tmp;
}
private void setFieldAccessor(FieldAccessor accessor, boolean overrideFinalCheck) {
this.overrideFieldAccessor = accessor;
}
}
{% endcode %}
上面是java.lang.reflect.Field
精简后的代码,可见 Field 类会在第一次调用 get 方法时会缓存 FieldAccessor,但是修改 Field 的修饰符不会清除缓存,所以还是用的老的 final 类型的 FieldAccessor。
结论:通过反射修改 Field 的修饰符必须在通过反射访问(get、set) Field 之后。