一、测试结论

static final 修饰的基本类型和String类型不能通过反射修改;

二、测试案例

@Test
public void test01() throws Exception {
setFinalStatic(Constant.class.getDeclaredField("i1"), 11);
System.out.println(Constant.i1); setFinalStatic(Constant.class.getDeclaredField("i2"), 22);
System.out.println(Constant.i2); setFinalStatic(Constant.class.getDeclaredField("s1"), "change1");
System.out.println(Constant.s1); setFinalStatic(Constant.class.getDeclaredField("s2"), "change2");
System.out.println(Constant.s2); System.out.println("----------------"); setFinalStatic(CC.class.getDeclaredField("i1"), 11);
System.out.println(CC.i1); setFinalStatic(CC.class.getDeclaredField("i2"), 22);
System.out.println(CC.i2); setFinalStatic(CC.class.getDeclaredField("i3"), 33);
System.out.println(CC.i3); setFinalStatic(CC.class.getDeclaredField("s1"), "change1");
System.out.println(CC.s1); setFinalStatic(CC.class.getDeclaredField("s2"), "change2");
System.out.println(CC.s2); setFinalStatic(CC.class.getDeclaredField("s3"), "change3");
System.out.println(CC.s3); } private void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
} interface Constant {
int i1 = 1;
Integer i2 = 1;
String s1 = "s1";
String s2 = new String("s2");
} static class CC {
private static final int i1 = 1;
private static final Integer i2 = 1;
private static Integer i3 = 1;
private static final String s1 = "s1";
private static final String s2 = new String("s2");
private static String s3 = "s3";
}
// 打印结果
1
22
s1
change2
----------------
1
22
33
s1
change2
change3

从打印的日志可以看到,正如开篇所说,除了 static final 修饰的基本类型和String类型修改失败,其他的都修改成功了;

但是这里有一个很有意思的现象,在debug的时候显示 i1 已经修改成功了,但是在打印的时候却任然是原来的值;

就是因为这个debug然我疑惑了很久,但是仔细分析后感觉这是一个bug,详细原因还暂时未知;

三、案例分析

private void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}

首先这里修改 static final 值得原理是,将这个 Field 的 FieldAccessor 的 final 给去掉了,否则在 field.set(null, newValue); 的时候, 就会检查 final 而导致失败

// UnsafeIntegerFieldAccessorImpl
if (this.isFinal) {
this.throwFinalFieldIllegalAccessException(var2);
}

而我们在 CC.class.getDeclaredField("i1") 获取的 Field 其实是 clazz 对象中的一个备份,

// Class
private static Field searchFields(Field[] fields, String name) {
String internedName = name.intern();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getName() == internedName) {
return getReflectionFactory().copyField(fields[i]);
}
}
return null;
} Field copy() {
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Field"); Field res = new Field(clazz, name, type, modifiers, slot, signature, annotations);
res.root = this;
// Might as well eagerly propagate this if already present
res.fieldAccessor = fieldAccessor;
res.overrideFieldAccessor = overrideFieldAccessor; return res;
}

所以在 field.set(null, newValue); 设置新值得时候,这里就应该是类似值传递和引用传递的问题,复制出来的 field 其实已经修改成功了,但是 root 对象仍然是原来的值,而在打印的时候,其实是直接取的 root 对象的值;

private void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
// Object o1 = field.get(null);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
Object o1 = field.get(null);
} // 打印 11

注意如果这里在去掉 final 之前就取了一次值,就会 set 失败, 因为 Class 默认开启了 useCaches 缓存, get 的时候会获取到 root field 的 FieldAccessor, 后面的重设就会失效;

四、字节码分析

这个问题还可以从字节码的角度分析:

public class CC {
public static final int i1 = 1;
public static final Integer i2 = 1;
public static int i3 = 1;
public final int i4 = 1;
public int i5 = 1;
}

// javap -verbose class

警告: 二进制文件CC包含com.sanzao.CC
Classfile /Users/wangzichao/workspace/test/target/classes/com/sanzao/CC.class
Last modified 2020-7-8; size 572 bytes
MD5 checksum 5f5847cb849315f98177420057130de6
Compiled from "CC.java"
public class com.sanzao.CC
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #7.#29 // com/sanzao/CC.i4:I
#3 = Fieldref #7.#30 // com/sanzao/CC.i5:I
#4 = Methodref #31.#32 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#5 = Fieldref #7.#33 // com/sanzao/CC.i2:Ljava/lang/Integer;
#6 = Fieldref #7.#34 // com/sanzao/CC.i3:I
#7 = Class #35 // com/sanzao/CC
#8 = Class #36 // java/lang/Object
#9 = Utf8 i1
#10 = Utf8 I
#11 = Utf8 ConstantValue
#12 = Integer 1
#13 = Utf8 i2
#14 = Utf8 Ljava/lang/Integer;
#15 = Utf8 i3
#16 = Utf8 i4
#17 = Utf8 i5
#18 = Utf8 <init>
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 LocalVariableTable
#23 = Utf8 this
#24 = Utf8 Lcom/sanzao/CC;
#25 = Utf8 <clinit>
#26 = Utf8 SourceFile
#27 = Utf8 CC.java
#28 = NameAndType #18:#19 // "<init>":()V
#29 = NameAndType #16:#10 // i4:I
#30 = NameAndType #17:#10 // i5:I
#31 = Class #37 // java/lang/Integer
#32 = NameAndType #38:#39 // valueOf:(I)Ljava/lang/Integer;
#33 = NameAndType #13:#14 // i2:Ljava/lang/Integer;
#34 = NameAndType #15:#10 // i3:I
#35 = Utf8 com/sanzao/CC
#36 = Utf8 java/lang/Object
#37 = Utf8 java/lang/Integer
#38 = Utf8 valueOf
#39 = Utf8 (I)Ljava/lang/Integer;
{
public static final int i1;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1 public static final java.lang.Integer i2;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL public static int i3;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC public final int i4;
descriptor: I
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: int 1 public int i5;
descriptor: I
flags: ACC_PUBLIC public com.sanzao.CC();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field i4:I
9: aload_0
10: iconst_1
11: putfield #3 // Field i5:I
14: return
LineNumberTable:
line 3: 0
line 7: 4
line 8: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/sanzao/CC; static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_1
1: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: putstatic #5 // Field i2:Ljava/lang/Integer;
7: iconst_1
8: putstatic #6 // Field i3:I
11: return
LineNumberTable:
line 5: 0
line 6: 7
}
SourceFile: "CC.java"
   #9 = Utf8               i1
#10 = Utf8 I
#11 = Utf8 ConstantValue
#12 = Integer 1

从这里就能看到 i1 其实是在编译的时候就已经初始化了(代码内联)优化, 而 i4, i5 是在构造函数的时候初始化, i2, i3 是在执行 static 阶段初始化, 同时 i2, i3, i4, i5 都会指向一个 Fieldref 对象, 所以在运行阶段就能通过 Fieldref 反射到它真实的值;

反射修改 static final 变量的更多相关文章

  1. Java反射-修改字段值, 反射修改static final修饰的字段

    反射修改字段 咱们从最简单的例子到难, 一步一步深入. 使用反射修改一个private修饰符的变量name 咱们回到主题, 先用反射来实现一个最基础的功能吧. 其中待获取的name如下: public ...

  2. Java反射-修改private final成员变量值,你知道多少?

    大家都知道使用java反射可以在运行时动态改变对象的行为,甚至是private final的成员变量,但并不是所有情况下,都可以修改成员变量.今天就举几个小例子说明.  基本数据类型 String类型 ...

  3. C# 反射修改私有静态成员变量

    //动态链接库中PvsApiIfCtrl.Cls.Cls_Public类有一变量 private static string key="abcd";//下面通过反射的技术修改和获取 ...

  4. 【Java关键字-Interface】为什么Interface中的变量只能是 public static final

    三个关键字在接口中的存在原因:public:接口可以被其他接口继承,也可以被类实现,类与接口.接口与接口可能会形成多层级关系,采用public可以满足变量的访问范围: static:如果变量不是sta ...

  5. 利用反射修改final数据域

    当final修饰一个数据域时,意义是声明该数据域是最终的,不可修改的.常见的使用场景就是eclipse自动生成的serialVersionUID一般都是final的. 另外还可以构造线程安全(thre ...

  6. Java 反射修改类的常量值、静态变量值、属性值

    前言 有的时候,我们需要修改一个变量的值,但变量也许存在于 Jar 包中或其他位置,导致我们不能从代码层面进行修改,于是我们就用到了下面的场景,通过反射来进行修改变量的值. 定义一个实体类 class ...

  7. java基础之final/static/static final

    一.final 1.final修饰变量(常量) final修饰的成员变量表示常量,一旦给定初值既无法改变 2.final方法 final修饰方法,表示该方法不能被子类重写 好处:比非final方法要快 ...

  8. JAVA 构造器, extends[继承], implements[实现], Interface[接口], reflect[反射], clone[克隆], final, static, abstrac

    记录一下: 构造器[构造函数]: 在java中如果用户编写类的时候没有提供构造函数,那么编译器会自动提供一个默认构造函数.它会把所有的实例字段设置为默认值:所有的数字变量初始化为0;所有的布尔变量设置 ...

  9. 为什么接口要规定成员变量必须是public static final的呢?(转)

    在interface里面的变量默认都是public static final 的.所以可以直接省略修饰符: String param="ssm"://变量需要初始化 为什么接口要规 ...

随机推荐

  1. Myeclipse 2014 破解补丁以及Y2课件迅雷下载

    一. 在破解myeclipse2014之前,要先把环境变量配置好: 1)打开我的电脑--属性--高级--环境变量2)新建系统变量JAVA_HOME 和CLASSPATH 变量名:JAVA_HOME 变 ...

  2. 如何通过PR给视频添加字幕?

    第一步:将视频通过导出音频格式MP3 第二步:将音频MP3导入网易见外平台 第三步:在网易见外平台创建项目,进行语音转写如下所示: 第四步:将从网易见外平台到处的srt字母文件,打开后进行编码为utf ...

  3. mysql字符串类型(char,varchar)

    原文链接:https://blog.csdn.net/puqutogether/article/details/45648879 MySQL中的字符串有两个常用的类型:char和varchar,二者各 ...

  4. webpack介绍—上

    6.1 webpack概念的引入 在网页中会引用哪些常见的静态资源? JS .js. .jsx ..coffee. .ts(TypeScript 类 C# 语言) CSS .css. .less. . ...

  5. 人脸识别和手势识别应用(face++)开发

    基础认识 本项目使用的是face++平台,人脸识别+手势识别双确认显示. python编程,代码简介,方便扩展. 该项目适用于Windows系统和Linux系统,但必须安装相应的模块,其中包括 l  ...

  6. android activity状态的保存

    今天接到一个电面,途中面试官问到一个问题,如果一个activity在后台的时候,因为内存不足可能被杀死,在这之前如果想保存其中的状态数据,比如说客户填的一些信息之类的,该在哪个方法中进行. onSav ...

  7. disruptor架构四 多生产者多消费者执行

    1.首先介绍下那个时候使用RingBuffer,那个时候使用disruptor ringBuffer比较适合场景比较简单的业务,disruptor比较适合场景较为复杂的业务,很多复杂的结果必须使用di ...

  8. pikachu靶场-XSS

    .Tips: 一般查询接口容易出现反射型XSS,留言板容易出现存储型XSS 由于后台可能存在过滤措施,构造的script可能会被过滤掉,而无法生效,或者环境限制了执行(浏览器): 通过变化不同的scr ...

  9. .net core 使用Tu Share获取股票交易数据

     一.什么是Tu Share Tushare是一个免费.开源的python财经数据接口包.主要实现对股票等金融数据从数据采集.清洗加工 到 数据存储的过程,用户可以免费(部分数据的下载有积分限制)的通 ...

  10. 入门大数据---Flink核心概念综述

    一.Flink 简介 Apache Flink 诞生于柏林工业大学的一个研究性项目,原名 StratoSphere .2014 年,由 StratoSphere 项目孵化出 Flink,并于同年捐赠 ...