Java 利用反射修改 static + final修饰的成员变量的值

注:本文收集自网络博客

调用对应Class的getDeclaredFieldgetField方法,获取要修改的Filed;2个方法的差别在于:

  • getDeclaredField可获取当前Class内所有变量名(private,protect,public,friend),但不会获取父类变量;
  • getField可获取当前Class及父Class内所有访问级别为public的变量名;

Java 利用反射修改 static + final 修饰的成员变量的值的步骤:

  1. final修饰的常量不可修改,判断field对应数据为常量则直接返回false

    常量的判断条件:
    1)使用了final修饰
    2)数据类型为基本类型或者String类型

    原因:使用final修饰后,被引用的数据地址将不可改变,我们只能尝试修改地址上的内容,而常量不能修改地址内容,或者说修改不生效。

  2. 如果访问级别不是public,调用setAccessible(true)获得访问权限;

  3. 如果使用了final修饰,而没有使用static修饰,可以调用setAccessible(true)获得修改权限,或者修改Modifier,去除final修饰符;如果同时使用了staticfinal,则只能通过修改Modifier去除final修饰符来获取修改权限;

  4. 判断要修改的数据类型,如果为基本类型,调用对应的基本类型修改方法,其他情况直接调用set方法;

  5. 对修改过的部分还原。

综合上述步骤,对应代码如下:

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 + finalMap

思路:不能停服,那就只能动态修改了,那必然用到反射。反射的一些基础知识请自行学习

参考: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是通过对象池创建的,则不会改变。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值