Java反射如何有效的修改final属性值详解
前言
以前写过一篇Java反射修改final属性值,本文将在这里重新温习一下Java反射如何有效的修改final属性值,下面话不多说了,来一起看看详细的介绍:
假设有个类
classPerson{ publicfinalStringname="Mike"; }
这里声明name为非静态的属性只是为了说明反射修改final属性无关乎静态不静态,静态只是表现在它是一个类属性,在一个类加载器空间只会有一份拷贝,仅此而已。
创建一个通用方法进行反射修改属性值
publicstaticvoidmodify(Objectobject,StringfieldName,ObjectnewFieldValue)throwsException{ Fieldfield=object.getClass().getDeclaredField(fieldName); FieldmodifiersField=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属性
Personperson=newPerson(); modify(person,"name","Michael"); System.out.println(person.name);//输出仍然是Mike
为什么代码执行下来没问题,但输出又还是原来的值呢?但总是可以通过反射方式获取到修改后的新值。这就是Java 编译器对final属型的内联优化,即编译时把该final的值直接放到了引用它的地方。即使是反射修改了该属性,但这种事后处理于事无补。
所以我在标题中所说如何有效的修改final属性值是指:反射修改了final属性值后能够在后续代码中使用修改后的值。
如果Person有个方法
publicStringgetName(){ returnname; }
在前面代码反射修改了name属性后,getName()方法也仍然是返回"Mike"。
那么Java会对什么类型的final值进行内联编译呢?它们基本类型byte,char,short,int,long,float,double,boolean;再加上LiteralString类型(直接双引号字符串)。只要是不被编译器内联优化的final属性都可以通过反射有效的进行修改(修改后能使用到新的值)
String类型比较特殊,如果把Person类name属性改成用newString("Mike")
publicfinalStringname=newString("Mike");
就能够有效的使用上面的反射代码修改name的值,最后
modify(person,"name","Michael"); System.out.println(person.name)//输出就是Michael了
正如基本类型有相应的包装类型,可以把newString("Mike")想像为"Mike"的包装类型。也确实是基本类型的final包装类型可以被反射有效的修改,newString("Mike")也可以。
但我们要是在IDE中写成newString("Mike"),可能被告知 newString("")这种格式是冗余的,IDE又会把它改成"Mike",重而反射行为变得不正确。
还有,如果final属性值是通过构造函数传入的能不能有效的被修改,当然可以,因为它也不会被编译器内联优化。
总之一句话:只要不会被编译器内联优化的final属性就可以通过反射有效的进行修改-- 修改后代码中可使用到新的值
为加深印象,我们最后作一个较全面的测试
publicclassTestFinal{ publicstaticvoidmain(String[]args)throwsException{ Personjordan=newPerson("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",newAddress("ccc","ddd")); System.out.println(jordan.address.line1);//ccc modify(jordan,"city","Miami"); System.out.println(jordan.city);//Miami } } classPerson{ publicfinalStringfirstName="Mike"; publicfinalStringlastName=newString("Jordan");//可被有效修改 publicfinalfloatage=50.5f; publicfinalFloatheight=1.99f;//可被有效修改 publicfinalAddressaddress=newAddress("aaa","bbb");//可被有效修改 publicfinalStringcity;//可被有效修改 publicPerson(Stringcity){ this.city=city; } } finalclassAddress{ publicfinalStringline1; publicfinalStringline2; publicAddress(Stringline1,Stringline2){ this.line1=line1; this.line2=line2; } }
我以前也有一人误区:就是总以为加了final关键值,该属性就会被编译器内联优化,就不能用反射有效的进行修改。通过今天的梳理终于清晰的明白了:final属性,只要不是基本类型和字面String,就可以正常使用反射修改它的值。以前曾为了想反射修改某个final值还特意把 final 关键字给去掉,完全不用这么做。所以对于基本类型和字面String,final只是预示着它的值不能被正常的代码修改。
因此我们在声明log时
privatestaticLoggerlog=LoggerFactory.getLogger(TestFinal.class);
和
privatestaticfinalLoggerlog=LoggerFactory.getLogger(TestFinal.class);
性能上不会有什么差异,也不会因为多一个final而在任何使用到了log的地方内联它。只要留意基本类型和字面String就行了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。