反射修改字段

咱们从最简单的例子到难, 一步一步深入.

使用反射修改一个private修饰符的变量name

咱们回到主题, 先用反射来实现一个最基础的功能吧.

其中待获取的name如下:

public class Pojo {
private StringBuilder name = new StringBuilder("default"); public void printName() {
System.out.println(name);
}
}

接下来咱们 使用反射来修改上面name的值.

为什么要用反射呢? 因为成员变量name是private修饰的, 而且没有提供一个setter方法.没有方法可以设置name的值.

虽然没有一个对外开放的接口, 但是反射却可以轻而易举地做到:

        Pojo p = new Pojo();

        // 查看被修改之前的值
p.printName(); // 反射获取字段, name成员变量
Field nameField = p.getClass().getDeclaredField("name"); // 由于name成员变量是private, 所以需要进行访问权限设定
nameField.setAccessible(true); // 使用反射进行赋值
nameField.set(p, new StringBuilder("111")); // 打印查看被修改后的值
p.printName();

发现被修改成功, 结果如下:

使用反射修改一个final修饰符的变量name

刚才使用反射成功修改了private修饰的变量, 那么如果是final修饰的变量那么还能否使用反射来进行修改呢? (因为正常的setter getter操作反正是做不到.)

声明一个final修饰的name如下. 接下来使用反射来对它进行修改. 目的也就是使name指向一个新的StringBuilder对象.

public class Pojo2 {
private final StringBuilder name = new StringBuilder("default2"); public void printName() {
System.out.println(name);
}
}

  咱们看看反射的威力吧, 它能修改final的字段的指向.也就是让name字段指向一个新的地址.

        Pojo2 p = new Pojo2();

        // 查看被修改之前的值
p.printName(); // 反射获取字段, name成员变量
Field nameField = p.getClass().getDeclaredField("name"); // 由于name成员变量是private, 所以需要进行访问权限设定
nameField.setAccessible(true); // 使用反射进行赋值
nameField.set(p, new StringBuilder("111")); // 打印查看被修改后的值
p.printName();

发现设置成功, 结果如下:

使用反射修改一个final修饰符的String类型变量name

如果说同学们在看我这篇文章时, 在前面偷懒了, 或者是认为StringBuilder和String没什么大区别, 于是就在前面把我代码里的StringBuilder都改为了String, 那么大家的执行结果将会是一个意外结果.

也就是我前面的例子用StringBuilder就能成功, 如果都替换成了String, 使用反射也不能够成功赋值.

为什么呢?

在讲解为什么之前, 我这里把这个问题重现一下:

把前面的StringBuilder替换为String后的Pojo3

public class Pojo3 {
private final String name = "default3"; public void printName() {
System.out.println(name);
}
}

使用反射尝试着进行赋值:

        Pojo3 p = new Pojo3();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "111");
p.printName();

发现赋值失败, 结果如下:


再一次提问: 为什么呢?

因为JVM在编译时期, 就把final类型的String进行了优化, 在编译时期就会把String处理成常量, 所以 Pojo3里的printName()方法, 就相当于:

public void printName() {
System.out.println("default3");
}

其实name的值是赋值成功了, 只是printName()方法在JVM优化后就被写死了, 所以无论name是否被正确修改为其他的值, printName始终都会打印"default3".

那么怎么知道name是不是真的被重新赋值成功了呢?

看下面代码:

        Pojo3 p = new Pojo3();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true); // 使用反射向name进行重新赋值
nameField.set(p, "111"); // 再使用反射再把name值取出来
Object name = nameField.get(p); // 把取出来的name值进行打印
System.out.println(name.toString());

结果如下, 说明name变量确实被赋值成功.  


那么可能有同学就问了,final修饰的String在JVM编译时就被处理为常量,  怎么样防止这种现象呢?   请看下面讲解

使用反射修改一个final修饰符的String类型变量name, 同时防止字符串在编译时被处理为常量

使用一些手段让final String类型的name的初始值经过一次运行才能得到, 那么就不会在编译时期就被处理为常亮了.

public class Pojo4 {
// 防止JVM编译时就把"default4"作为常量处理
private final String name = (null == null ? "default4" : ""); public void printName() {
System.out.println(name);
}
}

  运行测试的还是那段反射代码:

        Pojo4 p = new Pojo4();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "111");
p.printName();

结果如下, 发现确实成功了:  


那么有同学就会问了, 除了上面这种方法外, 还有什么方法能防止JVM在编译时就把final String的变量处理为常亮呢 ?

答: 嗯...只要是让name的值经过运行才能获得, 那么就不会被处理为常量. 我再举个程序例子吧.看下面代码:

public class Pojo5 {
private final String name = new StringBuilder("default5").toString(); public void printName() {
System.out.println(name);
}
}

  还是那段反射的代码, 运行

    @Test
public void test5() throws Exception {
Pojo5 p = new Pojo5();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "111");
p.printName();
}

结果如下, OK

使用反射修改一个static修饰符的变量name

刚才展示了使用反射来修改final修饰的字段, 接下来就演示一下使用反射来修改static修饰的变量:

如下的一个static修饰的一个name变量.

public class Pojo6 {
private static StringBuilder name = new StringBuilder("default6"); public void printName() {
System.out.println(name);
}
}

还是那段反射代码来进行测试:

        Pojo6 p = new Pojo6();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, new StringBuilder("111"));
p.printName();

发现结果如下, 也可以设置成功.  

使用反射修改final + static修饰符的变量name

一个同时被final和static修饰的变量如下所示:

public class Pojo7 {
private final static StringBuilder name = new StringBuilder("default7"); public void printName() {
System.out.println(name);
}
}

  如果还是通过下面这段反射代码来进行修改name的值, 那么就错了!

        Pojo7 p = new Pojo7();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, new StringBuilder("111"));
p.printName();

执行之后会报出如下异常, 因为反射无法修改同时被static final修饰的变量:

那到底能不能修改呢?

答案是能修改.

那怎么样修改呢?

思路是这样的, 先通过反射把name字段的final修饰符去掉.看如下代码:

先把name字段通过反射取出来, 这个和之前的步骤都一样, 反射出来的字段类型(Field)命名为'nameField'

        Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);

接下来再通过反射, 把nameField的final修饰符去掉:

        Field modifiers = nameField.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

然后就可以正常对name字段进行值的修改了.

        nameField.set(p, new StringBuilder("111"));

最后别忘了再把final修饰符加回来:

modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

本例子中反射部分完整的代码如下:

        // 注释的这段代码这样使用是错误的
// Pojo7 p = new Pojo7();
// p.printName();
// Field nameField = p.getClass().getDeclaredField("name");
// nameField.setAccessible(true);
// nameField.set(p, new StringBuilder("111"));
// p.printName(); Pojo7 p = new Pojo7();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true); Field modifiers = nameField.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); nameField.set(p, new StringBuilder("111"));
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
p.printName();

结果如下, 表示修改正确:  

本文中的所有代码都在这里: https://github.com/GoldArowana/K-Object/tree/master/src/test/java/reflect/field   里面的TestField.java为主要反射代码

Java反射-修改字段值, 反射修改static final修饰的字段的更多相关文章

  1. Final修饰的字段是否可以通过反射设置值

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

  2. Java中final与 static final 修饰的常量的区别

    喵喵开车,新手上路,多多关照.有任何错误请在评论区指出. ...........................................我是万恶的分界线( • ̀ω•́ )✧......... ...

  3. 为什么java的接口的方法是public abstract修饰?为什么属性是public static final 修饰?

     为什么java的接口的方法是public abstract修饰? 1.首先要明白接口的定义和作用是什么: 接口定义:接口是一个全部由抽象方法组成的集合,里面都是抽象方法和常量,用interface修 ...

  4. String的实例化与static final修饰符

    String两种实例化方式 一种是通过双引号直接赋值的方式,另外一种是使用标准的new调用构造方法完成实例化.如下: String str = "abcd"; String str ...

  5. 并不是static final 修饰的变量都是编译期常量

    见代码 public class Test { public static void main(String[] args){ // 情况一 基本数据类型 //System.out.println(O ...

  6. mysql数据库中,如何对json数据类型的值进行修改?通过json_set函数对json字段值进行修改?

    需求描述: 今天在看mysql中存放json数据类型的问题,对于json数据进行修改的操作, 在此记录下. 操作过程: 1.创建包含json数据类型的表,插入基础数据 mysql> create ...

  7. SQL Server 中根据字段值查询其所在的表、字段

    DECLARE @what varchar(800)SET @what='123456' --要搜索的字符串   DECLARE @sql varchar(8000)   DECLARE TableC ...

  8. private static final 修饰符

    java修饰符分类修饰符字段修饰符方法修饰符根据功能同主要分下几种 1.权限访问修饰符 public,protected,default,private,四种级别修饰符都用来修饰类.方法和字段 包外 ...

  9. 构造函数为何不能用abstract, static, final修饰

    不同于方法,构造器不能是abstract, static, final的. 1.构造器不是通过继承得到的,所以没有必要把它声明为final的. 2.同理,一个抽象的构造器将永远不会被实现,所以它也不能 ...

随机推荐

  1. 数据结构:关键路径,利用DFS遍历每一条关键路径JAVA语言实现

    这是我们学校做的数据结构课设,要求分别输出关键路径,我查遍资料java版的只能找到关键路径,但是无法分别输出关键路径 c++有可以分别输出的,所以在明白思想后自己写了一个java版的 函数带有输入函数 ...

  2. 工具资源系列之给虚拟机装个centos

    前文我们已经讲解了如何在 mac 系统上安装虚拟机软件,这节我们接着讲解如何利用虚拟机安装 centos 镜像. 安装镜像的大致步骤基本相同,只不过是配置项略显不同而已,如果需要安装其他系统镜像,请参 ...

  3. Python进阶教程001内置数据类型

    关于Python的基础知识已经告一段落了,我们接下来深入的研究Python的使用方法,以及以后将要使用到的类库. 格式化字符串 Python是支持字符串的格式化输出的,在之前的学习中我们也遇到过和使用 ...

  4. mssql server for docker on MacOs

    1. install 1.下载镜像 docker pull microsoft/mssql-server-linux 使用该命令就可以把数据库的docker镜像下载下来. 2.创建并运行容器 dock ...

  5. docker下编译mangoszero WOW60级服务端(三)

    开始构建WOW服务端通用镜像 第二篇文章中准备工作环节已经从github拉取了mangosd源代码,这里我们就可以直接开始编写dockerfile并进行编译 (1) 进入mangos/wow60/ma ...

  6. SQLSTATE[HY000]: General error: 1030 Got error 28 from storage engine

    今天上课程化平台考试,输入平台网址突然报这个错误 可以先df -h 发现/tmp文件使用满了 ,清理下不需要的临时文件即可

  7. 《Python 数据库 GUI CGI编程》

    本文地址:http://www.cnblogs.com/aiweixiao/p/8390417.html 原文地址 点击关注微信公众号 wenyuqinghuai 1.写在前边 上一次,我们介绍了Py ...

  8. java 向上向下取整

    Math.floor(1.4)=1.0 Math.round(1.4)=1 Math.ceil(1.4)=2.0 Math.floor(1.5)=1.0 Math.round(1.5)=2 Math. ...

  9. nginx 常见正则匹配符号表示

    1.^: 匹配字符串的开始位置: 2. $:匹配字符串的结束位置: 3..*: .匹配任意字符,*匹配数量0到正无穷: 4.\. 斜杠用来转义,\.匹配 . 特殊使用方法,记住记性了: 5.(值1|值 ...

  10. [原创]基于SpringAOP开发的方法调用链分析框架

    新人熟悉项目必备工具!基于SpringAOP开发的一款方法调用链分析插件,简单到只需要一个注解,异步非阻塞,完美嵌入Spring Cloud.Dubbo项目!再也不用担心搞不懂项目! 很多新人进入一家 ...