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修饰的字段被初始化了,引用的地址就不能发生变化,但是堆中的对象是可以被修改的。而字符串常量是不能被修改的,所以出现了,反射和实例去取出的数据不一致情况。

 

posted @ 2019-03-07 12:57  kancy  阅读(1330)  评论(0编辑  收藏  举报