zoukankan      html  css  js  c++  java
  • Final修饰的字段是否可以通过反射设置值

    案发现场

    经常听说final修饰的字段是常量不能改变的他的值,但是以外发现 Integer.java源码中的字段“value”是final,但是可以通过反射改变他的值。

    public final class Integer extends Number implements Comparable<Integer> {
      /**
         * The value of the {@code Integer}.
         *
         * @serial
         */
       private final int value;
      public Integer(String s) throws NumberFormatException {
            this.value = parseInt(s, 10);
        }
      public Integer(int value) {
            this.value = value;
        }
    }

    验证final修饰的字段是否可以修改

    对final修饰的成员非静态变量做强制修改

    package test;
    
    import java.lang.reflect.Field;
    
    /**
     * @author kancy
     * @version 1.0
     * @date 2019/3/7 12:28
     */
    public class FinalDemo {
        /**常量:默认值null*/
        private final String v1 = null;
        /**常量:默认值v4*/
        private final String v2 = "v2";
    
        public static void main(String[] args) throws Exception {
            FinalDemo finalDemo = new FinalDemo();
            Field f1 = finalDemo.getClass().getDeclaredField("v1");
            Field f2 = finalDemo.getClass().getDeclaredField("v2");
            f1.setAccessible(true);
            f2.setAccessible(true);
    
            // 反射改变v1的值
            f1.set(finalDemo, "new_v1");
            System.out.println("v1 改变后的值(对象取值):" + finalDemo.getV1());
            System.out.println("v1 改变后的值(反射取值):" + f1.get(finalDemo));
            // 反射改变v2的值
            f2.set(finalDemo, "new_v2");
            System.out.println("v2 改变后的值(对象取值):" + finalDemo.getV2());
            System.out.println("v2 改变后的值(反射取值):" + f2.get(finalDemo));
            System.out.println("--------------------------------------------------------");
            // 反射改变v1的值
            f1.set(finalDemo, "new_new_v1");
            System.out.println("v1 再次改变后的值(对象取值):" + finalDemo.getV1());
            System.out.println("v1 再次改变后的值(反射取值):" + f1.get(finalDemo));
            // 反射改变v2的值
            f2.set(finalDemo, "new_new_v2");
            System.out.println("v2 再次改变后的值(对象取值):" + finalDemo.getV2());
            System.out.println("v2 再次改变后的值(反射取值):" + f2.get(finalDemo));
        }
    
        public String getV1() {
            return v1;
        }
    
        public String getV2() {
            return v2;
        }
    }

    结果打印:

    v1 改变后的值(对象取值):new_v1
    v1 改变后的值(反射取值):new_v1
    v2 改变后的值(对象取值):v2
    v2 改变后的值(反射取值):new_v2
    --------------------------------------------------------
    v1 再次改变后的值(对象取值):new_new_v1
    v1 再次改变后的值(反射取值):new_new_v1
    v2 再次改变后的值(对象取值):v2
    v2 再次改变后的值(反射取值):new_new_v2

    结论:对于非静态的final成员变量,在没有赋值的情况下是可以使用反射对其进行赋值的;对于已经初始化赋值的变量,反射不能真正该变变量的值,但是使用反射get是可以获取到改变后的值,用实例是无法获取到的。

    对final修饰的成员非静态变量做强制修改

    import java.lang.reflect.Field;
    
    /**
     * @author kancy
     * @version 1.0
     * @date 2019/3/7 12:35
     */
    public class StaticFinalDemo {
        /**
         * 静态常量 默认值null
         */
        private static final String v1 = null;
        /**
         * 静态常量 默认值v3
         */
        private static final String v2 = "v2";
    
        public static void main(String[] args) throws Exception {
            Field f1 = StaticFinalDemo.class.getDeclaredField("v1");
            Field f2 = StaticFinalDemo.class.getDeclaredField("v2");
            f1.setAccessible(true);
            f2.setAccessible(true);
    
            // 反射改变v1的值
            try {
                f1.set(StaticFinalDemo.class, "new_v1");
                System.out.println("v1 改变后的值(对象取值):" + StaticFinalDemo.getV2());
                System.out.println("v1 改变后的值(反射取值):" + f1.get(StaticFinalDemo.class));
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            // 反射改变v2的值
            try {
                f2.set(StaticFinalDemo.class, "new_v2");
                System.out.println("v2 改变后的值(对象取值):" + StaticFinalDemo.getV2());
                System.out.println("v2 改变后的值(反射取值):" + f2.get(StaticFinalDemo.class));
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    
        public static String getV1() {
            return v1;
        }
    
        public static String getV2() {
            return v2;
        }
    }

    结果:

    java.lang.IllegalAccessException: Can not set static final java.lang.String field StaticFinalDemo.v1 to java.lang.String
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
    at java.lang.reflect.Field.set(Field.java:758)
    at StaticFinalDemo.main(StaticFinalDemo.java:26)
    java.lang.IllegalAccessException: Can not set static final java.lang.String field StaticFinalDemo.v2 to java.lang.String
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
    at java.lang.reflect.Field.set(Field.java:758)
    at StaticFinalDemo.main(StaticFinalDemo.java:36)

    结论:对静态的final修饰的字段(赋值或者不赋值)进行修改,会报错:java.lang.IllegalAccessException错误。

     反射的set的过程

    https://www.edrawsoft.cn/viewer/public/s/13c34261013087

    如果final修饰的类型为Object引用类型

    package test;
    
    import java.lang.reflect.Field;
    
    /**
     * @author kancy
     * @version 1.0
     * @date 2019/3/7 12:28
     */
    public class FinalDemo {
        /**常量:默认值null*/
        private final String v1 = null;
        /**常量:默认值v4*/
        private final String v2 = "v2";
        private final ChildA v3 = new ChildA("kancy");
        private final ChildA v4 = null;
    
        public FinalDemo() {
        }
    
        public static void main(String[] args) throws Exception {
            FinalDemo finalDemo = new FinalDemo();
            Field f3 = finalDemo.getClass().getDeclaredField("v3");
            f3.setAccessible(true);
            ChildA oldValue = finalDemo.getV3();
            System.out.println("v3 改变前的值:" + finalDemo.getV3()+", " );
            f3.set(finalDemo, new ChildA("pmm"));
            System.out.println("v3 改变后的值(对象取值):" + finalDemo.getV3()+", ");
            System.out.println("v3 改变后的值(反射取值):" + f3.get(finalDemo)+", ");
            // 地址已经发生变化
            System.out.println(oldValue == finalDemo.getV3());
    
        }
    
        public ChildA getV3() {
            return v3;
        }
    
        public static class ChildA {
            private String name;
    
            public ChildA(String name) {
                this.name = name;
            }
    
            public String getName() {
                return name;
            }
        }
    }

    结果:

    v3 改变前的值:test.FinalDemo$ChildA@66d3c617, 
    v3 改变后的值(对象取值):test.FinalDemo$ChildA@63947c6b, 
    v3 改变后的值(反射取值):test.FinalDemo$ChildA@63947c6b, 
    false
    
    进程已结束,退出代码 0
    

    结论:可以看出实例和反射取出的对象都是同一个,而使用字符类型就不是同一种类型,这是因为字符存在于常量池中,而对象存在与堆区。field.set是直接通过unsafe操作内存的,一但fianl修饰的字段被初始化了,引用的地址就不能发生变化,但是堆中的对象是可以被修改的。而字符串常量是不能被修改的,所以出现了,反射和实例去取出的数据不一致情况。

  • 相关阅读:
    ab命令做压测测试
    用js两张图片合并成一张图片
    Web全景图的原理及实现
    深入理解Java中的IO
    Spring AOP详解
    spring @Transactional注解参数详解
    优化MyBatis配置文件中的配置
    使用MyBatis对表执行CRUD操作
    @requestBody注解的使用
    url 拼接的一个模块furl
  • 原文地址:https://www.cnblogs.com/kancy/p/10488923.html
Copyright © 2011-2022 走看看