Java反照由浅入深|升级必备版权声明
原创这篇博文主要记录了我的学习 Java 反射(reflect在您了解反射之前,您应该首先了解 Java 中的 Class 同学们,如果你们不是很了解,可以先简单了解一下。
一、Java 反射机制
参考了许多博客文章,我总结了以下个人观点。如果有什么不对的地方,我希望改正:
Java 反映机制在程序中。 运行时 对于任何类,您都可以知道该类的所有属性和方法,而对于任何对象,您可以调用其任何方法和属性。这 动态获取信息 以及 动态调用对象的方法。 的函数被调用 java 反思机制 。
反射机制的一个重要方面是“运行时”,它允许我们在程序运行时的编译过程中加载、探索和使用完全未知的内容。
.class档案。换句话说,Java 该程序可以加载运行时以知道该名称。.class文件,然后学习它的完整结构,并生成它的对象实体或它的 fields(变量)设置一个值,或调用它。 methods(方法)。
我不知道你是否能理解上面的理论。无论如何,当我第一次触摸倒影时,我感到困惑。后来,我写了几个例子:哦~~这就是它的意思!
暂时不懂理论不要紧,先往下看例子,再回头看,相信自己能懂。
2.使用反射获取类的相关信息。
为了使测试结果更加明显,我首先定义了一个 FatherClass 类(默认情况下继承自 Object 类),然后定义从。 FatherClass 类的 SonClass 类,如下所示。可以看出,测试类中变量和方法的访问权限并不是很规范,都是为了更清楚地查看测试结果而刻意设置的。在实际项目中不建议这样做。
FatherClass.java
public class FatherClass {
public String mFatherName;
public int mFatherAge;
public void printFatherMsg(){}
}
复制代码
SonClass.java
public class SonClass extends FatherClass{
private String mSonName;
protected int mSonAge;
public String mSonBirthday;
public void printSonMsg(){
System.out.println("Son Msg - name : "
+ mSonName + "; age : " + mSonAge);
}
private void setSonName(String name){
mSonName = name;
}
private void setSonAge(int age){
mSonAge = age;
}
private int getSonAge(){
return mSonAge;
}
private String getSonName(){
return mSonName;
}
}
复制代码
-
获取类的所有变量信息。
/**
-
通过反射获取类的所有变量。 */ private static void printFields(){ //1.获取并输出类的名称 Class mClass = SonClass.class; System.out.println("班级名称:" + mClass.getName());
//2.1 获取所有 public 访问权限的变量 // 包括此类声明的类和从父类继承的类 Field[] fields = mClass.getFields();
//2.2 获取在此类中声明的所有变量(没有访问权限) //Field[] fields = mClass.getDeclaredFields();
//3. 遍历变量并输出变量信息 for (Field field : fields) { //获取访问和输出。 int modifiers = field.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //输出变量的类型和变量名。 System.out.println(field.getType().getName()
- " " + field.getName()); } } 复制代码
-
以上代码注释非常详细,不会对其进行解释。重要的是要注意到,在注释中 2.1 的 getFields() 与 2.2的 getDeclaredFields() 这两种情况之间的区别,下面看看两种情况的输出。请看前面的重点: SonClass extends FatherClass extends Object :
-
调用
getFields()方法,输出SonClass类及其继承的父类。( 包括FatherClass和Object) 的public方法。注:Object该类中没有成员变量,因此没有输出。班级名称:obj.SonClass public java.lang.String mSonBirthday public java.lang.String mFatherName public int mFatherAge 复制代码 -
调用
getDeclaredFields(), 输出SonClass类的所有成员变量,没有访问权。班级名称:obj.SonClass private java.lang.String mSonName protected int mSonAge public java.lang.String mSonBirthday 复制代码
-
获取类的所有方法信息。
/**
-
通过反射获取类的所有方法。 */ private static void printMethods(){ //1.获取并输出类的名称 Class mClass = SonClass.class; System.out.println("班级名称:" + mClass.getName());
//2.1 获取所有 public 访问方法 //包括它们自己的声明和从父类的继承。 Method[] mMethods = mClass.getMethods();
//2.2 获取此类的所有方法(没有访问权限) //Method[] mMethods = mClass.getDeclaredMethods();
//3.遍历所有方法 for (Method method : mMethods) { //获取和输出对该方法的访问(Modifiers:修改器) int modifiers = method.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); //获取并输出该方法的返回值类型。 Class returnType = method.getReturnType(); System.out.print(returnType.getName() + " "
- method.getName() + "( ");
//获取并输出该方法的所有参数。
Parameter[] parameters = method.getParameters();
for (Parameter parameter:
parameters) {
System.out.print(parameter.getType().getName()
- " " + parameter.getName() + ",");
}
//获取并输出该方法引发的异常。
Class[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length == 0){
System.out.println(" )");
}
else {
for (Class c : exceptionTypes) {
System.out.println(" ) throws "
- c.getName()); } } } } 复制代码
- " " + parameter.getName() + ",");
}
//获取并输出该方法引发的异常。
Class[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length == 0){
System.out.println(" )");
}
else {
for (Class c : exceptionTypes) {
System.out.println(" ) throws "
- method.getName() + "( ");
//获取并输出该方法的所有参数。
Parameter[] parameters = method.getParameters();
for (Parameter parameter:
parameters) {
System.out.print(parameter.getType().getName()
-
与获取变量信息一样,您需要注意注释。 2.1 与 2.2 不同之处,请看下面的打印输出:
-
调用
getMethods()方法 获取SonClass类所有public访问方法,包括从父类继承的。打印信息中,printSonMsg()方法来自SonClass类,printFatherMsg()来自FatherClass类,其余的方法来自 Object 类。班级名称:obj.SonClass public void printSonMsg( ) public void printFatherMsg( ) public final void wait( ) throws java.lang.InterruptedException public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException public final native void wait( long arg0, ) throws java.lang.InterruptedException public boolean equals( java.lang.Object arg0, ) public java.lang.String toString( ) public native int hashCode( ) public final native java.lang.Class getClass( ) public final native void notify( ) public final native void notifyAll( ) 复制代码 -
调用
getDeclaredMethods()方法在打印的信息中,输出为
SonClass类的方法不要求访问。班级名称:obj.SonClass private int getSonAge( ) private void setSonAge( int arg0, ) public void printSonMsg( ) private void setSonName( java.lang.String arg0, ) private java.lang.String getSonName( ) 复制代码
3.访问或操作类的私有变量和方法。
上面,我们成功地获取了类的变量和方法信息,并在运行时进行了验证 动态获取信息 视野。所以,只是为了获取信息?让我们回顾一下。
每个人都知道对象是私有变量和方法,不能在类上访问或操作,但我们可以通过反射来实现。是的,反思是可以做到的!接下来,让我们探索如何使用反射访问。 类对象的私有方法。 以及修改 私有变量或常量 。
像往常一样,首先测试类。
注:
- 请注意测试类中变量和方法的修饰符(访问权限);
- 测试类仅用于测试,在实际开发过程中不提倡这样编写。 : )
TestClass.java
public class TestClass {
private String MSG = "Original";
private void privateMethod(String head , int tail){
System.out.print(head + tail);
}
public String getMsg(){
return MSG;
}
}
复制代码
3.1 访问私有方法
以访问 TestClass 类中的私有方法 privateMethod(...) 例如,该方法增加了参数来考虑最完整的情况,这是很周到的吗?首先发布代码,查看注释,最后我将重点解释一些代码。
/**
* 访问对象的私有方法。
* 为获得简洁的代码,请在方法中抛出一个完全异常,并且在实际开发中不要这样做。
*/
private static void getPrivateMethod() throws Exception{
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有方法
//第一个参数是要获取的私有方法的名称。
//第二个是获取方法的参数类型,即。 Class...,则没有参数是null
//方法参数也可以用这种方式编写。 :new Class[]{String.class , int.class}
Method privateMethod =
mClass.getDeclaredMethod("privateMethod", String.class, int.class);
//3. 一种启动操作方法
if (privateMethod != null) {
//访问私有方法。
//只是获得访问权限,而不是修改实际权限。
privateMethod.setAccessible(true);
//使用 invoke 反射调用私有方法。
//privateMethod 是否获得了私有方法
//testClass 要操作的对象
//后两个参数传递实数参数。
privateMethod.invoke(testClass, "Java Reflect ", 666);
}
}
复制代码
值得注意的是,第一个3步中的 setAccessible(true) 方法,是访问私有方法。限,如果不加会报异常 IllegalAccessException 因为当前的方法访问是“private“,详情如下:
java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers "private"
复制代码
正常运行后,打印如下: 调用私有方法 成功:
Java Reflect 666
复制代码
3.2 修改私有变量
以修改 TestClass 类中的私有变量 MSG 例如,其初始值为 "Original" ,我们想要修改 "Modified"。像往常一样,首先转到代码查看注释。
/**
* 修改对象私有变量的值。
* 为获得简明的代码,请在该方法上抛出一个完全异常。
*/
private static void modifyPrivateFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有变量
Field privateField = mClass.getDeclaredField("MSG");
//3. 操作私有变量
if (privateField != null) {
//访问私有变量。
privateField.setAccessible(true);
//修改私有变量并输出以进行测试。
System.out.println("Before Modify:MSG = " + testClass.getMsg());
//调用 set(object , value) 修改变量的值
//privateField 是否已获取私有变量
//testClass 要操作的对象
//"Modified" 是要修改的值
privateField.set(testClass, "Modified");
System.out.println("After Modify:MSG = " + testClass.getMsg());
}
}
复制代码
这里的代码类似于访问私有方法的逻辑,所以我不再重复。从输出信息可以看出, 修改私有变量 成功:
Before Modify:MSG = Original
After Modify:MSG = Modified
3.3 修改私有常量
在 3.2 在中,我们介绍了如何修改私有 变量 现在我们来讨论一下如何修改私有 常量 ,
01. 它真的可以被修改吗?
常量是指使用 final 修饰符的成员属性和变量之间的区别在于是否存在修饰符。 final 关键字装饰。在说这句话之前,先补充一个知识点。
Java 虚拟机(JVM)在编译 .java 文件得到 .class 文件,我们优化我们的代码以提高效率。其中一项优化是:JVM 在编译阶段,引用常量的代码将替换为特定的常量值,如下所示(部分代码)。
编译前的 .java 文件:
//注意是 String 类型的值
private final String FINAL_VALUE = "hello";
if(FINAL_VALUE.equals("world")){
//do something
}
复制代码
已编译 .class 文件(当然,编译时没有注释):
private final String FINAL_VALUE = "hello";
//替换为"hello"
if("hello".equals("world")){
//do something
}
复制代码
然而,并不是所有的常量都是优化的。测试 int 、 long 、 boolean 以及 String 这些基本类型 JVM 将进行优化,同时 Integer 、 Long 、 Boolean 这种类型的包装或其他类似的包装 Date 、 Object 类型未进行优化。
总而言之: 对于基本类型的静态常量,JVM 在编译阶段,引用此常量的代码将替换为特定的常量值。 。
所以,在实际开发中,如果我们想修改一个类的常量值,碰巧那个常量是基本类型的,它不是无能为力的吗?无论如何,我个人认为,除非修改源代码,否则真的没有出路!
这里所谓的无能为力,是指: 当程序运行时,我们仍然可以使用反射来修改常量的值(代码将在稍后进行验证), JVM 在编译阶段获得 .class 文件已将常量优化为特定值,而特定值直接在运行阶段使用,因此即使修改了常量的值,也没有意义。 。
下面我们将在测试类中验证这一点。 TestClass 将以下代码添加到类中:
//String 会被 JVM 优化
private final String FINAL_VALUE = "FINAL";
public String getFinalValue(){
//扰流板,将进行优化: return "FINAL" ,我们走着瞧
return FINAL_VALUE;
}
复制代码
接下来,修改常量的值。首先,转到代码。请仔细阅读评论:
/**
* 修改对象的私有常量的值。
* 为获得简洁的代码,请在方法中抛出一个完全异常,并且在实际开发中不要这样做。
*/
private static void modifyFinalFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有常量
Field finalField = mClass.getDeclaredField("FINAL_VALUE");
//3. 修改常量的值
if (finalField != null) {
//访问私有常量。
finalField.setAccessible(true);
//调用 finalField 的 getter 方法
//输出 FINAL_VALUE 修改前的值
System.out.println("Before Modify:FINAL_VALUE = "
+ finalField.get(testClass));
//修改私有常量
finalField.set(testClass, "Modified");
//调用 finalField 的 getter 方法
//输出 FINAL_VALUE 修改值
System.out.println("After Modify:FINAL_VALUE = "
+ finalField.get(testClass));
//使用对象来调用类。 getter 方法
//获取值和输出。
System.out.println("Actually :FINAL_VALUE = "
+ testClass.getFinalValue());
}
}
复制代码
上面的代码没有解释,评论很详细!要特别注意第一个3注意到步骤,然后看着输出,已经等不及了,擦亮你的眼睛:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = FINAL
复制代码
结果出来了:
在打印和修改第一句之前 FINAL_VALUE 有价值,没有异议;
第二句打印修改后的常量的值,表示 FINAL_VALUE 确实被反思修改了;
第三句话印到了 getFinalValue() 方法来获取 FINAL_VALUE 但仍然是初始值,导致修改无效!
你认为这个结果可信吗?怎么,你还是不相信吗?问我怎么知道的 JVM 编译后代码会被优化吗?让我们来看看 TestClass.java 文件已编译 TestClass.class 档案。为了避免说代码是我自己写的,我决定不粘贴代码,直接截图:

你看,有照片和真相, getFinalValue() 方法直接 return "FINAL" 好了!它还表明, 程序根据编译后的 .class 来执行的 。
顺便说一句,如果您有时间,可以尝试几种数据类型。如上所述,某些数据类型将不会被优化。你可以修改数据类型,根据我的想法进行尝试。如果你认为输出是不可靠的,直接看它。 .classs 文件中,您可以一目了然地看到哪些数据类型进行了优化。 ,它们都没有经过优化。让我们来谈谈下一个知识点。
02. 想办法修改它吧!
不能修改,你能忍受吗?别担心,我不知道你有没有发现,刚才的常量都是在声明时直接赋值的。您可能会想,常量在声明时不都是赋值的吗?没有任务,就没有错误?当然不是。
方法一
事实上,Java 允许我们在没有赋值的情况下声明常量,但必须在构造函数中赋值。你可能会问我为什么这么说,这就解释了:
让我们修改一下吧 TestClass 类,声明常量时不要赋值,然后添加构造函数并为其赋值,看看修改后的代码(代码的一部分)。 ):
public class TestClass {
//......
private final String FINAL_VALUE;
//在构造函数中分配一个常量。
public TestClass(){
this.FINAL_VALUE = "FINAL";
}
//......
}
复制代码
现在,我们调用上面发布的修改常量的方法,并发现输出如下:
Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = Modified
复制代码
纳尼,最后一句输出修改值了?对,修改成功了!想知道为啥,还得看编译后的 TestClass.class 文件的映射,图中有标签。
解释:我们将赋值放在构造函数中,这是我们的运行时。 new 该对象将只被调用,因此在编译阶段不会使用它,就像以前将直接值赋给常量一样。 getFinalValue() 方法已优化为返回常量值,但指向 FINAL_VALUE 这对于我们在运行阶段通过反射修改开放值是有意义的。但是,可以看到,该程序仍经过优化以优化构造函数中的赋值语句。再想想那句话 程序根据编译后的 .class 来执行的 ,相信你一定明白为什么会这样产出!
方法二
在往下看之前,请务必把顶部划得很清楚。接下来,让我们讨论一个修改,它可以在不使用构造函数的情况下成功地修改常量的值,但在原则上是这样。删除构造函数,并将声明常量的语句更改为使用三项式表达式赋值:
private final String FINAL_VALUE
= null == null ? "FINAL" : null;
复制代码
事实上,上面的代码相当于直接。 FINAL_VALUE 赋值 "FINAL",但他可以!至于为什么,你认为是这样的: null == null ? "FINAL" : null 它是在运行时计算的,不会在编译时进行计算和优化,因此您知道。
综上所述,无论是使用构造函数还是使用三项式表达式,基本上都是 避免在编译时进行优化。 ,以便我们在常量有意义之前通过反射修改常量!好了,这一小部分到此为止了!
最后强调 :
重要的是要提醒你,不要紧。 将值直接赋值给常量 、 通过构造函数为常量赋值 还是 使用三项式运算符 事实上,我们可以通过反射成功地修改常量的值。以及我上面所说的变化"成功"或者不是意味着: 我们当然可以在程序运行阶段通过反射修改常量值,但实际上是执行优化的 .class 修改后的值真的在文件中起作用吗?换句话说,是否在编译时将常量替换为特定值?如果它被替换了,无论如何修改常量的值,都不会影响最终的结果,对吗? 。
事实上,你可以直接思考: 反射肯定能修改常量的值,但修改值是否有意义 ?
03. 它可以改变吗?
它可以改变吗?也就是说反射修改后到底有没有意义?
如果你看清楚了,答案很简单。常言道,千言万语不如一图,让我用一个不太规范的流程图来直接表达答案。
注:图中"没法修改"可以理解"可以修改该值,但没有意义";"可以修改"是指"可以修改值并使其有意义"。

四、总结
嗯,这张唱片就在这里。突然,我发现我写了很多东西。谢谢你耐心地听我说。我想如果你仔细阅读这篇博客,你一定会得到一些东西的!最后,因为内容和知识点较多,如果课文中有任何错误或缺陷,请改正。欢迎留言!
作者:伯特
链接:https://juejin.im/post/598ea9116fb9a03c335a99a4
来源:掘金
版权归作者所有。请联系作者以获得商业转载的授权,并注明非商业转载的来源。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123



