目录
一、引言
- 在Java的学习中,大家总是顺向的去分析codes(代码),如果没有对某一个code已经了解过了,就很难逆向解析,那么,反射就应运而生了。
- 在学习反射(reflection)之前,大家需要去多看看API,主要看Class,Object,Exception,还有Java.until.reflect.*;包
- 此外,在Java反射中会有一些jvm的知识,有兴趣小伙伴可以看看《深入Java虚拟机》这本书。
- PS:红色标记表示十分重要,蓝色表示需要注意的细节
二、反射认知概念
1.反射是什么?
- 指程序可以访问、检测和修改它本身状态或行为的一种能力,简单来说就是 一种逆向解析本身code。
- java类中的各种成分被反射映射成一个个的Java类。
2.反射的使用前提是什么?
- 必须得到代表的字节码的Class,Class类用于表示.class文件(字节码)
class文件是编译器编译之后供虚拟机解释执行的二进制字节码文件,不只是java。故Class类是反射的根本。
3.反射的作用是什么?
- 增加了codes的灵活性
- 可以通过反射是程序代码访问在jvm中的类的内部信息。(主要是成员变量,方法,构造方法相关信息)
- 得到类的信息之后,根据类的信息去做一些特定的操作
4.反射有没有缺点?
- 反射绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
- 主要用在对灵活性和扩展性要求很高的系统框架上。随意使用会模糊程序内部结构逻辑。
PS:好啦,介绍了一些迷迷糊糊的东西,不过除了标出来的之外,可以过后再来理解。接下来进入正题!
三、反射codes部分开始
1.反射之根本——Class
-
Class介绍
Class在API中有如下两个要点: 第一,位于java.lang.Object包下。第二,实现了Serializable, AnnotatedElement, GenericDeclaration, Type接口,其中AnnotatedElement后面会写。
- 获取Class类对象(字节码)的三种方式:第一种方式最为重要,十分重要
package test2;
public class testClass {
public static void main(String[] args) throws Exception {//此处throws文末写,不会造成理解障碍
//方法一,从类的全名中获取
//forName("所属包名.类名");
Class ca=Class.forName("test2.WordCodes");//通常都是使用的该种方式
//方法二,从已有对象中获取
WordCodes wc=new WordCodes(); //已经有了WordCodes对象,就不需要用反射了,多此一举
Class ca2=wc.getClass();
//方法三,从已有的类中获取
Class ca3=WordCodes.class; //需要带入WordCodes所在的包,依赖性太强
}
}
class WordCodes{
//新建一个类,只是为了演示,故没写然后属性,方法。
}
2.目标类下的三大详细信息
首先,创造一个已经存在的类,用作为目标类,具备以下属性、方法:PS:请多留意一下他们的访问权限及方法的返回类型
package package1;
class testM{
//成员变量
public int number1 = 10;
private String na1 = "hello" ;
private int number2;
private String na2;
//构造方法
public testM(){
System.out.println("公有无参构造方法");
}
private testM(int age){
System.out.println("私有含参构造方法"+age);
}
public testM(String na2){
this.na2=na2;
System.out.println("公有含参构造"+na2);
}
//成员方法
public void methods1(){
System.out.println("公有无参的普通方法");
}
public void methods1(boolean b){
System.out.println("公有有参的普通方法+"+b);
}
private void methods2(){
System.out.println("私有无参的普通方法");
}
private void methods2(String s){
System.out.println("私有有参的普通方法 "+s);
}
}
- 构造函数的获取与使用
package package1;
import java.lang.reflect.Constructor;
public class SecondRe{
public static void main(String[] args) throws Exception {
Class c1= Class.forName("package1.testM"); //由于前面说了,该方法获取C最常用lass,故以此为示范
System.out.println("-------------获取所有构造方法------------");
Constructor[] C_All=c1.getDeclaredConstructors(); //getDeclaredConstructors为复数形式,表示有多个,且以数组形式报存
for(Constructor C_All_Print : C_All) //打印保存的所有构造方法的信息
System.out.println(C_All_Print);
System.out.println("-------------获取公有无参构造方法,实例化时自动调用-----------");
Constructor C = c1.getConstructor(); //注意用的是getConstructor,没有's'
System.out.println(C);
Object CC = C.newInstance(); //该句实例化了获取信息的对象。因为此处Class类把获取的信息一个个映射为了类
System.out.println("-------------获取私有含参构造方法并调用---------------");
Constructor con = c1.getDeclaredConstructor(String.class); //获取class对象
System.out.println(con);
con.setAccessible(true); //设置访问权限
Object obj=con.newInstance("nan"); //为含参构造设置相应的参数值,并且实例化对象调用
}
}
//对于Object CC = c1.getConstructor();句, 如果不需要接收该对象,可以直接调用c1.getConstructor();
结果如下:
分析介绍:(调用关系)
- 请注意,该操作使用了Class(根本),Constructor(操控构造方法的对象),Object(java.lang包下最大的类)
- Constructor extends--->Executable extends--->AccessibleObject extends--->Object
- Class extends--->Object
- Class类用来获取目标类的所有信息;Constructor以对象或者数组的身份去操控构造方法(接收了Class对象保存信息的一部分);Object类作为它们直接或间接的父类,相当于一个基类,创建的引用变量可以接收他们的对象;
PS:对newInstance的深入理解可以点这里直达;
- 成员变量的获取与调用
package package1;
import java.lang.reflect.Field;
public class SecondRe{
public static void main(String[] args) throws Exception {
Class c1= Class.forName("package1.testM");
System.out.println("-----------所有公有获取变量-------------");
Field[] f1 = c1.getFields();
for(Field ff:f1){System.out.println(ff);}
System.out.println("-----------公有变量调用-------------");
Field ff1 = c1.getField("number1");
System.out.println(ff1);
Object obj1=c1.newInstance(); //产生testM对象
// ff1.set(obj1,9); //为字段设置值,若该字段不想修改,也可以省去此句
System.out.println(ff1.get(obj1)); //可得到对象中名为number的值,注意,使用了Object的引用变量,get();方法是Filed类中的方法,格式为get(Object obj)
System.out.println("-----------获取私有变量-------------");
Field f2 = c1.getDeclaredField("na1");
System.out.println(f2);
System.out.println(f2.getName());
Object obj2= c1.newInstance();//产生testM对象
f2.setAccessible(true);//设置访问权限
//f2.set(obj2,"he"); // //为字段设置值,也可以省去此句
System.out.println(f2.get(obj2));
}
}
结果如下:
分析如下:
- 该程序的调用方式与构造的方式相似,故不在详解
- 对get(Object obj); 方法 API: Returns the value of the field represented by this
Field
, on the specified object. 表示返回该指定对象字段中的字段值- 同理,getName();方法 API:返回当前对象的名字。
- set(obj,value),为obj字段赋值,setAccessible();为当前对象设置访问权限,true为许可,false为禁止;
- 普通成员方法的获取与调用
-
结果如下:package package1; import java.lang.reflect.Method; public class SecondRe{ public static void main(String[] args) throws Exception { Class c1= Class.forName("package1.testM"); System.out.println("-----------获取所有公有方法------------"); Method[] M1=c1.getDeclaredMethods(); for(Method MM:M1){ System.out.println(MM); } System.out.println("-----------获取并调用公有方法------------"); Method MM1=c1.getMethod("methods1",boolean.class); System.out.println(MM1); Object ObMM1 = c1.newInstance(); //Object o=c1.getConstructor().newInstance(); //两种方式一样 MM1.invoke(ObMM1,true); System.out.println(MM1.getName()); System.out.println("-----------获取并调用私有方法------------"); Method MM2 = c1.getDeclaredMethod("methods2",String.class); Object OBMM2 = c1.newInstance(); MM2.setAccessible(true); MM2.invoke(OBMM2,"+私有噢+"); } }
分析如下:
- 获取与调用的方式与上述差不多,就是换了一个类来接收方法对象
- invoke(Object obj,Object... args) 调用具有指定参数的对象上的该方法
3.对三大详细信息的总结
- 主要使用了Method,Constructor,Field这三个类。使用这三个类的对象或者数组可以保存三大信息
- 该三大类的操作方法,结构十分相似,可以举一反三
- 对私有信息进行操作之前,给予对象访问权限,可暴力访问(setAccessible(true)),否则会抛出IllegalAccessException异常
- 三大类除了已列出的方法,自身还有许多方法,详情看看API文档,合适取用。
- 对于main函数的反射也相同。
package package1; import java.lang.reflect.Method; public class SecondRe{ public static void main(String[] args) throws Exception { Class c1= Class.forName("package1.testM"); Method methodMain = clazz.getMethod("main", String[].class); methodMain.invoke(null, (Object)new String[]{"a","b","c"}); } }
四、扩展部分
1.反射的其它使用:
- 通过反射运行配置文件的内容
- 通过反射越过泛型检查
- 内省与反射
2.扩展知识
- Class类被参数化,例如Class<Employee>的类型是Employee.class。它将已经抽象的概念更加复杂化了,在大多数实际问题中可以忽略类型参数,而使用原始的Class类。
- 异常的捕获与处理,因为反射经常会出现异常,例如空方法等,故需要做好安全问题
- 以上我们已返回值为void进行的,其余返回值类型,相似处理,类比使用就行。
PS:若有问题,欢迎指正。