java反射详解
一 什么是反射?
在计算机领域,反射是一种能力,能够自描述和自控制,即在运行状态中,动态获取类信息即动态调用实例方法的能力。
Java 反射有以下3个动态特性:
- 运行时创建
- 运行期间调用方法
- 运行时更改属性
首先 Java 程序的执行过程如下:
Java程序要想运行,Java类必须要被java虚拟机加载。
之前我们所运行的程序都是在编译时就已经链接了所有所需的类,而 Java反射 机制则允许程序在运行时在加载。
1.1 Java反射可以做什么呢?
通过java反射可以实现一下功能:
- 在运行时探知任意一个实例所属的类
- 在运行时构造任意一个类的实例
- 在运行时探知任意一个类所具有的方法和属性
- 在运行时调用任意一个类的实例方法
就像照镜子能够看清楚自己,反射使程序可以看清一个类的情况并加以使用,java反射机制能够探知类的基本结构,这种对java类结构探知的能力称为java类的“自审”。并且反射机制是构建框架技术的基础所在,掌握java反射机制,对以后学习框架有很大的帮助。
1.2 Java反射常用API
使用Java反射技术,常用的类如下:
- java.lang.Class < T > 类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类可以获取该类的属性,方法等内容信息。
- java.lang.reflect.Constructor< T >类:表示类的构造方法
- java.lang.reflect.Field类: 表示类的属性,可以获取和设置类中属性的值
- java.lang.reflect.Method类:表示类的方法,可以用来获取类中方法的信息或执行方法。
二 反射的应用
在java程序中使用反射的基本步骤如下:
1 导入java.lang.reflect包中的类
2 获取需要操作的类Class实例
3 调用Class实例的方法获取Field,Method等实例
4 使用 反射API操作实例成员
2.1 获取类的信息
1 获取Class实例
Java程序中获得Class实例通常方法如下3中方式,可根据实际情况灵活选择
方式一 :
// 调用类或接口实例的getClass()方法
Class clz= obj.getClass(); // obj 为某个类型的实例
getClass() 方法是java.lang.Obejct类中的一个方法,所有类和接口的实例都可以调用该方法,该方法会返回该实例的所属类型所对应的Class实例。例如,对于通过入参或返回值得到的某个实例获取其类型信息
方式二:
// 调用类或接口的class属性
Class clz = Student.class; // Student 为定义的学生类型
在某些类或接口没有实例或无法创建实例的情况下,可以通过器class属性获取所对应的Class实例,这种方式需要在编译期就知道类或接口的名称。
方法三:
// 使用Class.forName()方法
Class clz = Class.forName("com.mysql.cj.jdbc.Driver");
若编码时无法确认具体类型,需要程序在运行时根据情况灵活加载,可以使用Class类的forName()方法。该方法是静态方法,需要传入字符串参数,该字符串参数的值是某个类的完全限定类名,即包含包名的完整类名。
2 从Class实例获取信息
在获得某个类型对应的Class实例之后,就可以调用Class实例的方法来获得该类型的信息。Class类提供了大量的实例方法来获取对应类型的详细信息。
获取对应类型的基本信息,如下
方法 | 说明 |
---|---|
Strign getName() | 以字符串形式返回该类型的名称 |
String getSimpleName() | 以字符串形式返回该类型的简称 |
… | … |
获取对应类型所包含的构造方法的方法:
方法 | 说明 |
---|---|
Constructor getConstructor(Class … params) | 返回该类型指定参数列表的public 构造方法,构造方法的参数列表与params所指定的类型列表匹配,例如: Constructor co=clz.getCOnstructor(String.class,List.class); |
Constructor [] getCOnstructors() | 返回该类型的所有public构造方法 |
Constructor getDeclaredConstructor(Class…params) | 返回该类型的指定参数列表的构造方法。访问级别不限 |
COnstructor [] getDelaredConstructors() | 返回该类型的所有构造方法,访问级别不限 |
访问类包含方法的方法
方法 | 说明 |
---|---|
Method getMethod(String name , Class…params) | 返回该实例指定的public 方法,name 参数用于指定方法的名称,params参数指定参数列表,例如:clz.getMethod(“info”,String.class);//clz为某Class实例 clz.getMethod(“info”,String.class.Integer.class); |
Method[ ] getMethods() | 返回该实例中的所有方法 |
Method getDeclaredMethod(String name,Class…params) | 返回该实例中指定的方法,与方法的访问级别无关 |
Method [] getDeclaredMethods() | 返回该实例中的全部方法,与方法的访问级别无关 |
示例:
// 获取demo类中的所有方法
// 包括公共,保护,默认(包)访问和私有方法,但不包括继承方法
// 如果该类或接口不声明任何方法,或者此Class实例表示一个基本类型
// 一个数组或void,则此方法返回一个长度为0的数组
Method [] methods=Demo.class.geteDeclaredMethods(); // 返回该实例中的全部方法
// 展示方法的一些信息
for(Method method:methods){
System.out.println("方法名:"+method.getName());
System.out.println("返回类型"+method.getReturnType().getName());
//获取方法的参数列表
Class[] params=methods.getParameterType();
System.out.println("访问修饰符 : ");
int modifier=method.getModifiers();
//判断方法的访问修饰
//判断方法是否有static修饰符
if((medifier& Modifier.STATIC)==Midufuer.STATTIC){
System.out.println("这是一个静态方法");
}
//判断方法是否有final修饰符
if((modifier & Modifier.FINAL)==Midufuer.FINAL){
System.out.println("这是一个final方法");
}
}
2.2 创建实例
通过反射来创建Java的类型的实例有如下两种方式:
1 使用Class实例的newInstance()方法创建相关类型的实例。
2 使用Constructor实例创建相关类型的实例
使用Class.newInstance() 方法创建实例:
Class clz= Class.forName("reflect.entity.demo");// 加载demo类
Object obj= clz.newInstance(); //调用demo类的无参构造创建demo类的实例
System.out.println(obj);
如果创建Java实例时,需要使用指定的构造方法,则可以利用Constructor实例,每一个Constructor 实例对应一个构造方法,指定构造方法来创建Java对象的步骤如下:
1 获取与该类型相关的的Class实例
2 调用Class实例的方法获取表示指定构造方法的Constructor实例
3 调用COnstructor实例的newInstance()方法来创建相关类型的实例
Class clz=Class.forName("reflect.entity.demo");
// 获取demo的无参构造:public demo()
Constructor c1 = clz.getDeclaredConstructor();
// demo的无参构造方法为public ,这里可以直接访问
Object obj=c1.newInstance();
System.out.println(obj);
// 获取demo的单参构造方法:private demo(String)
Constructor c2 = clz.getDeclaredConstructor(String.class);
// demo的单参构造方法为private,这里已超出其访问范围,不能直接访问
// 通过setAccessable 方法,设定为可以访问
c2.setAccessible(true);
obj=c2.newInstance();
System.out.println(obj);
//获取demo的三参构造protected demo(String String String)
Constructor c3 = clz.getDeclaredConstructor(String.class,String.class,String.class);
//demo的三参构造为protected,这里已经超出其访问范围,不能直接访问
//通过setAccessable方法,设定为可以访问
c3.setAccessible(true);
obj=c3.newInstance("New demo","beijing","Hello!");
System.out.println(obj);
注意: 受访问修饰符的限制,使反射方式访问超出访问范围的构造方法,属性,方法时,会引发java.lang.IllegalAccessException异常,若要禁止Java语言访问检查强行访问,需要设置相关实例为可访问,语法如下:
c3.setAccessible(true); //通过构造方法、属性,方法实例调用
当然此做法会破坏封装,需谨慎使用。
1 访问类的属性
使用Field实例可以对属性进行取值或赋值操作
方法 | 说明 |
---|---|
xxx getXxx (Object obj) | xxx 表示8中基本类型之一, |
Object get(Object obj) | 以Object 类型返回obj中相关属性的值 |
void setXxx(Object obj,xxx val) | 将obj中相关属性的值设置为val,xxx为8种基本数据类型之一 |
void set(Obejct obj ) | 将obj相关属性设置为val |
void setAccessible(boolean flag) | 对相关属性设置访问权限。设置true可以禁止Java语言访问检查 |
通过反射方式访问Demo类的name 属性,实现取值和赋值。
//通过反射加载一个Demo实例
Class clz=Class.forName("com.entity.Demo");
Object obj=clz.newInstance();
//获取private String name 属性
Field name=clz.getDeclaredField("name");
// name 属性为private,这里已超出访问范围,不能直接访问
// 通过设置setAccessible()方法,设置可以访问
name.setAccessible(true);
//先取值看一下
System.out.println("赋值前的name:"+name.get(obj));
// 赋值
name.set(obj,"总有刁民想害朕");
//展示赋值结果
System.out.println("赋值后的结果"+name.get(obj));
注意:没有用getField()方法来获取属性,因为getField()方法只能获取public 访问权限的属性,而使用getDeclaredField()方法则可以获取所有访问权限的属性
2.3 调用类的方法
Method类中包含一个invoke()方法,通过invoke()方法,Method实例可以调用Java类的实例方法和静态方法。invoke()方法定义如下:
Object invoke(Object obj,Object...args);
obj 是执行该方法的对象,args是执行该方法时传入的参数。
例如,method表示A类中的fun()方法,instanceA类的实例,则method.invoke(instance,args)表示调用instance的fun()方法并传入参数args.
若Method实例表示的是一个静态方法,则obj可以为null。
通过反射方式调用Demo类的方法,包括实例方法和静态方法。
//通过反射加载Demo类
Class clz=Class.forName("com.entity.Demo");
// 根据方法名和参数列表获取static final int getAge(); 方法
// 没有参数可以不写或写成null
Method getAge=clz.getDeclaredMethod("getAge",null);
//getAge()为default(package),这里已经超出访问范围,不能直接访问
//通过设置setAccessible(true); 设定为可以问访问
getAge.setAccessible(true);
//调用getAge()并传参,没有参数可以不写或者用null表示
//getAge()为静态方法,调用时可以不指定具体Demo实例
Object obj=getAge.invoke(null,null);
System.out.println("年龄:"+obj);
Object demoObj=clz.newInstance(); //创建Demo实例
//根据方法名和参数列表获取 private void silentMenthod()方法
//没有参数可以不写或者写null
Method silentMethod=clz.getDeclaredMethod("silentMethod",null);
//silentMethod方法是private,超出访问范围,不能访问
// 设置setAccessible(true); 可以访问
silentMethod.setAccessible(true);
//调用silentMethod()并传参,没有参数可以不写或用null表示
silentMethod.invoke(demoObj,null);
//根据方法名和参数列表获取 public void setName(String)方法
Method setName=clz.getDeclaredMethod("setName",String.class);
//setName方法为public ,这里可以直接访问
setName.invoke(demoObj,"总有刁民想害朕");
//验证一下结果,调用public String getName()
Object returnName=clz.getDeclaredMethod("getName").invoke(demoObj);
System.out.println("name:"+returnName);
三 总结
注意:使用反射虽然会很大程度上提高代码的灵活性,但是不能滥用反射,因为通过反射创建和访问实例是性能要稍微低一点,且反射会破坏封装。实际上,只有当程序需要动态创建类的实例时才会考虑使用反射。
1 Java反射机制是指在运行状态中,动态获取类型信息及动态访问实例成员的能力
2 使用反射可以在程序运行时创建类的实例机访问其属性和方法。
3 反射在Java框架技术中有大量的应用。