当前位置: 首页 > 编程笔记 >

Java反射如何有效的修改final属性值详解

公羊奇
2023-03-14
本文向大家介绍Java反射如何有效的修改final属性值详解,包括了Java反射如何有效的修改final属性值详解的使用技巧和注意事项,需要的朋友参考一下

前言

以前写过一篇 Java 反射修改 final 属性值,本文将在这里重新温习一下Java反射如何有效的修改final属性值,下面话不多说了,来一起看看详细的介绍:

假设有个类

class Person {
  public final String name = "Mike";
}

这里声明 name 为非静态的属性只是为了说明反射修改 final 属性无关乎静态不静态,静态只是表现在它是一个类属性,在一个类加载器空间只会有一份拷贝,仅此而已。

创建一个通用方法进行反射修改属性值

public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception {
 Field field = object.getClass().getDeclaredField(fieldName);
 
 Field modifiersField = Field.class.getDeclaredField("modifiers");
 modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
 modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
 
 if(!field.isAccessible()) {
  field.setAccessible(true);
 }
 
 field.set(object, newFieldValue);
}

调用 modify(...) 方法试图修改 person 的 name 属性

Person person = new Person();
modify(person, "name", "Michael");
System.out.println(person.name); //输出仍然是 Mike

为什么代码执行下来没问题,但输出又还是原来的值呢?但总是可以通过反射方式获取到修改后的新值。这就是 Java  编译器对 final 属型的内联优化,即编译时把该 final 的值直接放到了引用它的地方。即使是反射修改了该属性,但这种事后处理于事无补。

所以我在标题中所说如何有效的修改 final 属性值是指:反射修改了 final 属性值后能够在后续代码中使用修改后的值。

如果 Person 有个方法

public String getName() {
 return name;
}

在前面代码反射修改了 name 属性后,getName() 方法也仍然是返回 "Mike"。

那么 Java 会对什么类型的 final 值进行内联编译呢?它们基本类型 byte, char, short, int, long, float, double, boolean; 再加上 Literal String 类型(直接双引号字符串)。只要是不被编译器内联优化的 final 属性都可以通过反射有效的进行修改(修改后能使用到新的值)

String 类型比较特殊, 如果把 Person 类 name 属性改成用 new String("Mike")

public final String name = new String("Mike");

就能够有效的使用上面的反射代码修改 name 的值,最后

modify(person, "name", "Michael");
System.out.println(person.name) //输出就是 Michael 了

正如基本类型有相应的包装类型,可以把 new String("Mike") 想像为 "Mike" 的包装类型。也确实是基本类型的 final 包装类型可以被反射有效的修改,new String("Mike") 也可以。

但我们要是在 IDE 中写成 new String("Mike") , 可能被告知  new String("") 这种格式是冗余的,IDE 又会把它改成 "Mike",重而反射行为变得不正确。

还有,如果 final 属性值是通过构造函数传入的能不能有效的被修改,当然可以,因为它也不会被编译器内联优化。

总之一句话:只要不会被编译器内联优化的 final 属性就可以通过反射有效的进行修改 --  修改后代码中可使用到新的值

为加深印象,我们最后作一个较全面的测试

public class TestFinal {
 public static void main(String[] args) throws Exception {
  Person jordan = new Person("Chicago");
 
  modify(jordan , "firstName", "Michael");
  System.out.println(jordan .firstName); //Mike
 
  modify(jordan , "lastName", "Michael");
  System.out.println(jordan.lastName); //Michael
 
  modify(jordan , "age", 51f);
  System.out.println(jordan .age); //50.5
 
  modify(jordan , "height", 1.98f);
  System.out.println(jordan.height); //1.98
 
  modify(jordan, "address", new Address("ccc", "ddd"));
  System.out.println(jordan.address.line1); //ccc
 
  modify(jordan , "city", "Miami");
  System.out.println(jordan.city); //Miami
 }
}
 
class Person {
 public final String firstName = "Mike";
 public final String lastName = new String("Jordan"); //可被有效修改
 public final float age = 50.5f;
 public final Float height = 1.99f; //可被有效修改
 public final Address address = new Address("aaa", "bbb"); //可被有效修改
 
 public final String city; //可被有效修改
 
 public Person(String city) {
  this.city = city;
 }
}
 
final class Address {
 public final String line1;
 public final String line2;
 
 public Address(String line1, String line2) {
  this.line1 = line1;
  this.line2 = line2;
 }
}

我以前也有一人误区: 就是总以为加了 final 关键值,该属性就会被编译器内联优化,就不能用反射有效的进行修改。通过今天的梳理终于清晰的明白了:final 属性,只要不是基本类型和字面 String,就可以正常使用反射修改它的值。以前曾为了想反射修改某个 final 值还特意把  final  关键字给去掉,完全不用这么做。所以对于基本类型和字面 String, final 只是预示着它的值不能被正常的代码修改。

因此我们在声明 log 时

private static Logger log = LoggerFactory.getLogger(TestFinal.class);

private static final Logger log = LoggerFactory.getLogger(TestFinal.class);

性能上不会有什么差异,也不会因为多一个 final 而在任何使用到了 log 的地方内联它。只要留意基本类型和字面 String 就行了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。

 类似资料:
  • 本文向大家介绍Java反射如何修改private final成员变量值,包括了Java反射如何修改private final成员变量值的使用技巧和注意事项,需要的朋友参考一下 大家都知道使用java反射可以在运行时动态改变对象的行为,甚至是private final的成员变量,但并不是所有情况下,都可以修改成员变量。今天就举几个小例子说明。 基本数据类型 测试代码: 运行结果:18 此时无法修改成

  • 本文向大家介绍Java 反射修改类的常量值、静态变量值、属性值实例详解,包括了Java 反射修改类的常量值、静态变量值、属性值实例详解的使用技巧和注意事项,需要的朋友参考一下 前言 有的时候,我们需要修改一个变量的值,但变量也许存在于 Jar 包中或其他位置,导致我们不能从代码层面进行修改,于是我们就用到了下面的场景,通过反射来进行修改变量的值。 定义一个实体类 利用反射修改私有静态常量方法 修改

  • 问题内容: 在Java中,两者之间有什么区别? 和 都是和,不同之处在于属性。 有什么更好的?又为什么呢? 问题答案: 通常,是指“与类型本身相关联,而不是与类型实例相关联”。 这意味着你可以在没有创建类型实例的情况下引用静态变量,并且任何引用该变量的代码都引用完全相同的数据。将其与实例变量进行比较:在这种情况下,该类的每个实例都有一个独立的变量版本。因此,例如: 打印出并且是分开的,因为x和y引

  • 本文向大家介绍Java 用反射设置对象的属性值实例详解,包括了Java 用反射设置对象的属性值实例详解的使用技巧和注意事项,需要的朋友参考一下 Java 用反射设置对象的属性值实例详解 Java 反射 创建对象的方法 1、通过Class对象的newInstance()方法,这种方法的局限是只能取调用默认的构造函数 2、通过取得Class对象的getConstructor方法取得Construtor

  • 反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如下表所示。 反射值对象的零值和有效性判断方法 方 法 说 明 IsNil() bool 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的操作 IsValid() bool 判断值是否有效。 当值本身非法时,返回 false,例如 reflect V

  • 主要内容:判定及获取元素的相关方法,值修改相关方法,值可修改条件之一:可被寻址,值可修改条件之一:被导出Go语言中类似 x、x.f[1] 和 *p 形式的表达式都可以表示变量,但是其它如 x + 1 和 f(2) 则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。 对于 reflect.Values 也有类似的区别。有一些 reflect.Values 是可取地址的;其它一些则不可以。考虑以下的声明语句: x := 2 // value type