Java 反射

java反射详解

一 什么是反射?

在计算机领域,反射是一种能力,能够自描述和自控制,即在运行状态中,动态获取类信息即动态调用实例方法的能力。

Java 反射有以下3个动态特性:

  1. 运行时创建
  2. 运行期间调用方法
  3. 运行时更改属性

首先 Java 程序的执行过程如下:
在这里插入图片描述
Java程序要想运行,Java类必须要被java虚拟机加载。
之前我们所运行的程序都是在编译时就已经链接了所有所需的类,而 Java反射 机制则允许程序在运行时在加载

1.1 Java反射可以做什么呢?

通过java反射可以实现一下功能:

  1. 在运行时探知任意一个实例所属的类
  2. 在运行时构造任意一个类的实例
  3. 在运行时探知任意一个类所具有的方法和属性
  4. 在运行时调用任意一个类的实例方法

就像照镜子能够看清楚自己,反射使程序可以看清一个类的情况并加以使用,java反射机制能够探知类的基本结构,这种对java类结构探知的能力称为java类的“自审”。并且反射机制是构建框架技术的基础所在,掌握java反射机制,对以后学习框架有很大的帮助。

1.2 Java反射常用API

使用Java反射技术,常用的类如下:

  1. java.lang.Class < T > 类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类可以获取该类的属性,方法等内容信息。
  2. java.lang.reflect.Constructor< T >类:表示类的构造方法
  3. java.lang.reflect.Field类: 表示类的属性,可以获取和设置类中属性的值
  4. 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框架技术中有大量的应用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值