注:本文收集自网络博客
调用对应Class的getDeclaredField
或getField
方法,获取要修改的Filed
;2个方法的差别在于:
getDeclaredField
可获取当前Class
内所有变量名(private,protect,public,friend
),但不会获取父类变量;getField
可获取当前Class
及父Class
内所有访问级别为public
的变量名;
Java 利用反射修改 static + final
修饰的成员变量的值的步骤:
-
final
修饰的常量不可修改,判断field
对应数据为常量则直接返回false
;常量的判断条件:
1)使用了final
修饰
2)数据类型为基本类型或者String
类型原因:使用
final
修饰后,被引用的数据地址将不可改变,我们只能尝试修改地址上的内容,而常量不能修改地址内容,或者说修改不生效。 -
如果访问级别不是
public
,调用setAccessible(true)
获得访问权限; -
如果使用了
final
修饰,而没有使用static
修饰,可以调用setAccessible(true)
获得修改权限,或者修改Modifier
,去除final
修饰符;如果同时使用了static
和final
,则只能通过修改Modifier
去除final
修饰符来获取修改权限; -
判断要修改的数据类型,如果为基本类型,调用对应的基本类型修改方法,其他情况直接调用
set
方法; -
对修改过的部分还原。
综合上述步骤,对应代码如下:
public static boolean setValue(@Nullable Object source, @NonNull Class<?> target, @NonNull String name, @Nullable Object value) {
Field field = null;
int modify = 0;
Field modifiersField = null;
boolean removeFinal = false;
try {
field = target.getDeclaredField(name);
modify = field.getModifiers();
//final修饰的基本类型不可修改
if (field.getType().isPrimitive() && Modifier.isFinal(modify)) {
return false;
}
//获取访问权限
if (!Modifier.isPublic(modify) || Modifier.isFinal(modify)) {
field.setAccessible(true);
}
//static final同时修饰
removeFinal = Modifier.isStatic(modify) && Modifier.isFinal(modify);
if (removeFinal) {
modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, modify & ~Modifier.FINAL);
}
//按照类型调用设置方法
if (value != null && field.getType().isPrimitive()) {
if ("int".equals(field.getType().getName()) && value instanceof Number) {
field.setInt(source, ((Number) value).intValue());
} else if ("boolean".equals(field.getType().getName()) && value instanceof Boolean) {
field.setBoolean(source, (Boolean) value);
} else if ("byte".equals(field.getType().getName()) && value instanceof Byte) {
field.setByte(source, (Byte) value);
} else if ("char".equals(field.getType().getName()) && value instanceof Character) {
field.setChar(source, (Character) value);
} else if ("double".equals(field.getType().getName()) && value instanceof Number) {
field.setDouble(source, ((Number) value).doubleValue());
} else if ("long".equals(field.getType().getName()) && value instanceof Number) {
field.setLong(source, ((Number) value).longValue());
} else if ("float".equals(field.getType().getName()) && value instanceof Number) {
field.setFloat(source, ((Number) value).floatValue());
} else if ("short".equals(field.getType().getName()) && value instanceof Number) {
field.setShort(source, ((Number) value).shortValue());
} else {
return false;
}
} else {
field.set(source, value);
}
} catch (Exception e) {
return false;
} finally {
try {
//权限还原
if (field != null) {
if (removeFinal && modifiersField != null) {
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
modifiersField.setAccessible(false);
}
if (!Modifier.isPublic(modify) || Modifier.isFinal(modify)) {
field.setAccessible(false);
}
}
} catch (IllegalAccessException e) {
//
}
}
}
问题:今天项目有一个需求,需要new
一个HashMap
,将它赋值给一个修饰符为 static + final
的Map
。
思路:不能停服,那就只能动态修改了,那必然用到反射。反射的一些基础知识请自行学习
参考:1、http://my.oschina.net/dxqr/blog/215504?p={{totalPage}}
2、http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection
代码:
/**
* 修改静态final字段的值
* @author chenzl
* 2015-09-22
*/
public class SetFinalValue {
public static final Map<Integer, Integer> openMap = new HashMap<Integer, Integer>();
public static void main(String[] args) throws Exception {
Field target = SetFinalValue.class.getField("openMap");
int modify = target.getModifiers();
SetFinalValue.checkModifier(modify);
Map<Integer, Object> openMap2 = new HashMap<Integer, Object>();
openMap2.put(2, "abcde");
try {
//取消 Java 语言访问检查,详细查看 API(这里可以不写)
target.setAccessible(true);
//获得修饰符Field对象,通过这个对象可以对另外一个Field对象的操作符进行修改,源码见图-1
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
//关于Modefier常量的定义见图-2
modify = target.getModifiers() & ~Modifier.FINAL;
System.out.println("处理后的 modify : " + modify);
//更改目标对象的修饰符
modifiersField.setInt(target, modify);
modify = target.getModifiers();
System.out.println("#####更改修饰符后的结果######");
SetFinalValue.checkModifier(modify);
//更改静态常量
target.set(null, openMap2);
System.out.println(openMap.get(2));
} catch (Exception e) {
e.printStackTrace();
}
/**重复设置一次*/
Map<Integer, Object> openMap3 = new HashMap<Integer, Object>();
openMap3.put(3, "中文输入");
try {
target = SetFinalValue.class.getField("openMap");
target.setAccessible(true);
System.out.println("#####重复一次检验一次重新get后值会不会改变######");
checkModifier(target.getModifiers());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 检查所有的修饰符,是否是 public static final
* @param modify
*/
public static void checkModifier(int modify){
System.out.println("当前的 modify : " + modify);
//源码见图-3
System.out.println(" public : " + Modifier.isPublic(modify));
System.out.println(" static : " + Modifier.isStatic(modify));
System.out.println(" final : " + Modifier.isFinal(modify));
}
}
程序结果:
总结:先拿到成员变量的Field对象,从Field对象中获得所有修饰符,修改它的修饰符,然后设置对象的值。
其他:
1、测试基本数据类型 int
2、测试Integer
类型:
3、String
类型
3.1 String
类型的其他方式
关于String
类型出现的特殊情况,我暂时也不知道原因,猜测是与String
类型的常量池有关,有待我以后去证实。
3.2 当我用一个新的常量去替换时成功了。
那么我暂时理解成 open变量指向常量池的一个内存地址,在编译器就确定了,不能动态指向常量池外的内存地址,只能重新指向常量池内的另外一个内存地址。
2017-7-29 修改:
jdk版本:1.8.0.31
文章的最后出现一个错误,3.2中的open是一个新new的对象,不管是直接传入“开始”还是传入str都可以修改它。如果open是通过对象池创建的,则不会改变。