zoukankan      html  css  js  c++  java
  • 反射修改 static final 变量

    一、测试结论

    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 已经修改成功了,但是在打印的时候却任然是原来的值;

    反射1

    就是因为这个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 反射到它真实的值;

  • 相关阅读:
    HDU 1058 Humble Numbers
    HDU 1160 FatMouse's Speed
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1003 Max Sum
    HDU 1297 Children’s Queue
    UVA1584环状序列 Circular Sequence
    UVA442 矩阵链乘 Matrix Chain Multiplication
    DjangoModels修改后出现You are trying to add a non-nullable field 'download' to book without a default; we can't do that (the database needs something to populate existing rows). Please select a fix:
    opencv做的简单播放器
    c++文件流输入输出
  • 原文地址:https://www.cnblogs.com/sanzao/p/13267269.html
Copyright © 2011-2022 走看看