反射
小马:现在是五点钟,今天天气不错,感觉小鱼想起来学习新知识,想想跟他说点啥呢?
ummmmm........ 看他头发挺多的,教他JAVA反射吧,就这样决定了。
打电话给小鱼....
小鱼接通电话..
小鱼:喂...咋了小马,我还在睡觉呢。
小马:睡啥睡啊,一日之计在于晨。今天要教你一个非常棒的东西。麻溜的起床
小鱼:......我昨天学接口学到三点多才睡觉,头发都掉了好多,你让我在睡会吧。
小马:(憋笑中....),行吧行吧,让你再睡十分钟。
小鱼:十分钟!??三十分钟!
小马:行行行,三十分钟就三十分钟。我三十分钟后叫你。
小鱼:好,谢谢小马宽宏大量,小马威武!
半个小时后......
打电话给小鱼....
小鱼接通电话..
小马:小鱼 起床学习啦!
小鱼:...... 好好好,那我们今天学习啥?
小马:今天学习的乃是java语言的灵魂,反射!!(此处应该有掌声)
小鱼:哇!好棒好棒。(内心:啥灵魂,我只想睡觉),那小马快说说吧。
小马:那我先给你说一下反射的前景吧:
java,c++,c# 这类语言不属于动态语言,而目前的结论是在程序运行中能运行改变变量类型和程序结构的被叫做动态语言,但 JAVA Reflection(反射) 具备获取class 属性,方法,构造 并且能够动态创建class,动态给class 中属性赋值,调用方法等强大的能力。
那么我们来简单概述一下吧:
java反射机制在运行状态中,对于任意一个类,都能获取到该类的所有属性和方法,并且能够调用任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法叫做JAVA反射。
那么他的运行原理是啥呢?
首先 我们新创建一个类A a = new A(); 虚拟机会加载 A.class(一个类只产生一个class),同时会产生对象空间 a , 反射就是动态获取到了A的class 对象后 再获取到对象的各种信息。
小鱼:好高深,有点难理解。
小马:确实会有点难理解,但是这一关是每个优秀程序员必走的。
小鱼:嗯嗯,好的我明白了,那我知道反射是能够动态获取程序运行中的class对象,那么它应用场景在哪呢?
小马:ummm,这个你好好听好啊。
虽然Java在编译的时候就必须知道 所引用的类所在地方,但是在实际编程中,在某些场景,可能需要调用一个不再编译空间的类,又或者需要获取一个未知的类的东西,这个时候使用常规的方法就不可以了,那么 JAVA的CLASS配合反射能够很好的解决这种情况,JAVA里面的反射可以帮助我们在运行程序的时候加载,在编译期间位置的class,简单来说就是JAVa中可以加一个运行时候才知道名称的CLASS,获取其完整的结构,并生成实例化对象,对其赋值,调用方法等。
再具体开发中,通过反射获取类的实例,大大提高系统的灵活性和可扩展性,但是由于反射的性能较低,并且极大破坏了类的封装性(通过反射可以获取到私有的属性和方法),在大部分场合下不适合使用反射,但是在一些框架中,会大范围使用反射来帮助框架完善一些功能。
打个比方:我们需要制作一个工具方法,而方法的用处是获取到传入的某个类的所有方法属性和值,但是我们并不知道传入的类具体是什么,只有在运行的时候才会知道,这时候我们可以通过反射,动态的获取到传入泛型的class信息,然后使用反射机制获取该类的方法属性和值。
小鱼:原来如此。那怎么使用它啊。
小马:使用它之前首先我们需要知道 jdk给我们提供反射机制的类。
Class : 在反射中表示内存中的一个Java类,Class可以代表的实例类型包括,类和接口、基本数据类型、数组
Object : Java中所有类的超类
Constructor: 封装了类的构造函数的属性信息,包括访问权限和动态调用信息
Field : 提供类或接口的成员变量属性信息,包括访问权限和动态修改
Method: 提供类或接口的方法属性信息,包括访问权限和动态调用信息
Modifier : 封装了修饰属性, public、protected、static、final、synchronized、
abstract等
注:Class本是一个类,而class是修饰符来标识这是一个类,记住C大写的Class是类,c小写的class是修饰符关键字。
我们根据上面关键词一个一个来说,首先是Class
首先我们定义一个使用的对象User
package com.study.reflectstudy;
import com.study.interfacestudy.BaseInterface;
import java.io.Serializable;
/**
* @author 博博
* @Title: User
* @time 2019/10/18 16:40
*/
public class User implements Serializable {
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private Integer pwd;
public User(String userName, Integer pwd) {
this.userName = userName;
this.pwd = pwd;
}
protected User(Integer pwd) {
this.pwd = pwd;
}
public User() {
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getPwd() {
return pwd;
}
public void setPwd(Integer pwd) {
this.pwd = pwd;
}
private void test(){
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", pwd=" + pwd +
'}';
}
}
1.Class
怎么获取一个class呢?
@Test
public void getClassStudy(){
try {
// 通过 Class.forName() 参数 类的全限定地址 获取到Class对象
Class<?> className = Class.forName("com.study.reflectstudy.User");
System.out.println(className.getSimpleName());
// 第二种方式 直接调用类的.class 获取到该类的Class对象
Class<User> userClass = User.class;
System.out.println(userClass.getSimpleName());
// 第三种方式 调用对象的getClass() 方法 获取到该对象的Class对象
User user = new User();
Class<? extends User> getClassUser = user.getClass();
System.out.println(getClassUser.getSimpleName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
输出结果:
User User User
这里我们可以看到我们通过三个方式获取了Class对象,同时我们使用了class对象中的方法getSimpleName()获取类对象名称,那我们来看看其他的几个常用的方法。
/**
* 基本方法学习
*/
@Test
public void getBaseStudy(){
// 首先通过类.class方式获取 Class对象
Class<User> userClass = User.class;
// 获取类名称
String simpleName = userClass.getSimpleName();
System.out.println(simpleName);
// 获取全限定名称
String name = userClass.getName();
System.out.println(name);
// 获取父类Class对象
Class<? super User> superclass = userClass.getSuperclass();
System.out.println(superclass.getName());
// 获取该类实现的接口数组,因为一个类可以实现多个接口,所以返回是数组
Class<?>[] interfaces = userClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}
}
输出结果
User
com.study.reflectstudy.User
java.lang.Object
java.io.Serializable
这是输出结果,使用的是Class对象最基本的操作。可能会问,为啥我明明什么都没有继承却输出了‘java.lang.Object’ ,这是因为,java中 一个类如果没有显式继承哪个类,就是隐式的继承Object类,所以可以看到我们没有继承Object 但是 输出了Object。那么还有个问题,为啥获取接口是返回的数组。
因为 一个类可以实现多个数组,所有返回的是一个数组对象。
看完基础的,可能会问,为啥上面介绍的其他几个反射类没有说啊,因为要从简至繁,那我们现在来讲讲,Class对象的灵魂,获取对象的属性方法调用方法赋值,以及实例化对象
那么首先我们先来看一下 Constructor: 封装了类的构造函数的属性信息,包括访问权限和动态调用信息
/**
* 灵魂 构造函数调用
*/
@Test
public void getSoulConstructorStudy() throws IllegalAccessException, InstantiationException, NoSuchMethodException {
// 首先通过类.class方式获取 Class对象
Class<User> userClass = User.class;
// 第二步我们使用反射实例化的对象
User user = userClass.newInstance();
// 赋值,方便之后操作
user.setUserName("mhb");
user.setPwd(2345);
System.out.println("该类的构造函数:getConstuctors(), 获取全部public的");
// 获取构造函数的信息
Constructor<?>[] constructors = userClass.getConstructors();
// 定义修饰符
int modify;
for (Constructor<?> constructor : constructors) {
// 获取构造函数的修饰符
modify = constructor.getModifiers();
System.out.println("修饰符:" + Modifier.toString(modify) + ",名称:" + constructor.getName());
}
System.out.println();
System.out.println("该类的构造函数:getConstuctors() 根据参数 String Integer 获取public类型的");
// 获取构造函数的信息
Constructor<User> userClassConstructor = userClass.getConstructor(String.class, Integer.class);
// 获取构造函数的修饰符
int modifyParam = userClassConstructor.getModifiers();
System.out.println("修饰符:" + Modifier.toString(modifyParam) + ",名称:" + userClassConstructor.getName());
System.out.println();
System.out.println("该类的构造函数 getDelaredConstructors(),获取所有的构造函数");
Constructor<?>[] userClassDeclaredConstructors = userClass.getDeclaredConstructors();
for (Constructor<?> userClassDeclaredConstructor : userClassDeclaredConstructors) {
System.out.println("修饰符:" + Modifier.toString(userClassDeclaredConstructor.getModifiers()) + ",名称:" + userClassDeclaredConstructor.getName());
}
System.out.println();
System.out.println("该类的构造函数 getDelaredConstructors(),根据参数类型 Integer 获取");
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(Integer.class);
System.out.println("修饰符:" + Modifier.toString(declaredConstructor.getModifiers()) + ",名称:" + declaredConstructor.getName());
}
输出结果:该类的构造函数:getConstuctors(), 获取全部public的
修饰符:public,名称:com.study.reflectstudy.User
修饰符:public,名称:com.study.reflectstudy.User该类的构造函数:getConstuctors() 根据参数 String Integer 获取public类型的
修饰符:public,名称:com.study.reflectstudy.User该类的构造函数 getDelaredConstructors(),获取所有的构造函数
修饰符:public,名称:com.study.reflectstudy.User
修饰符:protected,名称:com.study.reflectstudy.User
修饰符:public,名称:com.study.reflectstudy.User该类的构造函数 getDelaredConstructors(),根据参数类型 Integer 获取
修饰符:protected,名称:com.study.reflectstudy.User
可以看到 我们使用 class.newInstance(); 来实例化一个对象,并进行赋值,这里学习调用了四个方法,
1.getConstuctors(),获取的构造函数全部是public属性的。
2.getConstuctor(Class … params),根据参数,从所有public属性的构造函数中获取相关构造函数
3.getDelaredConstructors(),获取所有的构造函数
4.getDelaredConstructor(Class … params),根据参数,从所有的构造函数中获取相关构造函数
学习了Constructor 是不是被反射的强大惊艳,那么我们来看看常用之二的Field 属性操作
/**
* 灵魂 属性调用
*/
@Test
public void getSoulFieldStudy() throws IllegalAccessException, InstantiationException, NoSuchMethodException, NoSuchFieldException {
// 首先通过类.class方式获取 Class对象
Class<User> userClass = User.class;
// 第二步我们使用反射实例化的对象
User user = userClass.newInstance();
// 赋值,方便之后操作
user.setUserName("mhb");
user.setPwd(2345);
// 获取所有属性
Field[] fields = userClass.getDeclaredFields();
System.out.println(" getDeclaredFields():获取类的所有成员变量");
for (Field field : fields) {
// 打破私有规则,无视访问级别
field.setAccessible(true);
/*
field.getName() 返回一个属性的名称
field.get(user) get的值则是需要获取值的对象,返回对象中对应属性的值
field.getModifiers() 返回一个属性中的修饰符,int类型,需要配合Modifier.toString()来解析
Modifier.toString 会根据返回的数值来判定是哪种修饰符
field.getType() 返回属性类型,方法使用方法和Class类使用方法一样,这里
field.getType().getSimpleName() 返回类型名称
*/
System.out.println("属性名称:" + field.getName() + ",属性值:" + field.get(user) +
",属性修饰符" + Modifier.toString(field.getModifiers()) + ",属性类型名称" + field.getType().getSimpleName());
}
System.out.println();
System.out.println("getFields():获取类的所有public属性的成员变量");
Field[] fields1 = userClass.getFields();
for (Field field : fields1) {
System.out.println("属性名称:" + field.getName() + ",属性值:" + field.get(user) +
",属性修饰符" + Modifier.toString(field.getModifiers()) + ",属性类型名称" + field.getType().getSimpleName());
}
System.out.println();
System.out.println("getDeclaredField():获取类的所有成员变量 根据名称获取单个");
Field pwdField = userClass.getDeclaredField("pwd");
// 首先需要无视权限 , 在实际开发中不推荐这样做,最好通过调用setXXXX 、getXXX 方法赋值
pwdField.setAccessible(true);
System.out.println("属性名称:" + pwdField.getName() + ",属性值:" + pwdField.get(user) +
",属性修饰符" + Modifier.toString(pwdField.getModifiers()) + ",属性类型名称" + pwdField.getType().getSimpleName());
System.out.println("赋值:");
System.out.println("赋值前:"+pwdField.get(user));
// 调用set 方法赋值
pwdField.set(user,123);
System.out.println("赋值后:"+pwdField.get(user));
}
输出结果 :
getDeclaredFields():获取类的所有成员变量
属性名称:userName,属性值:mhb,属性修饰符private,属性类型名称String
属性名称:pwd,属性值:2345,属性修饰符private,属性类型名称IntegergetFields():获取类的所有public属性的成员变量
getDeclaredField():获取类的所有成员变量 根据名称获取单个
属性名称:pwd,属性值:2345,属性修饰符private,属性类型名称Integer
赋值:
赋值前:2345
赋值后:123
这里属性调用学习调用了四个方法:
1.getFields():获取类的所有public属性的成员变量
2.getDeclaredFields():获取类的所有成员变量
3.getMethod(name, params): 根据参数在getFields()获取的成员变量中进行筛选
4.getDeclaredMethod(name, params):根据参数在getDeclaredFields()获取的成员变量中进行筛选
需要注意:像setAccessible 这种破坏封装性的方法尽量不要使用。
常用之三 : Method 方法调用
/**
* 灵魂 方法调用
*/
@Test
public void getSoulMethodStudy() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 首先通过类.class方式获取 Class对象
Class<User> userClass = User.class;
// 第二步我们使用反射实例化的对象
User user = userClass.newInstance();
// 赋值,方便之后操作
user.setUserName("mhb");
user.setPwd(2345);
System.out.println("getMethods() 获取所有public定义的方法,包括继承下来的方法");
// 获取所有public方法
Method[] methods = userClass.getMethods();
for (Method method : methods) {
System.out.println("修饰符:" + Modifier.toString(method.getModifiers()) + ",方法名称" + method.getName() +
",返回值:" + method.getAnnotatedReturnType().getType().getTypeName());
}
System.out.println();
System.out.println("getDeclaredMethods() 获取本身声明的方法,包括复写的方法,不包括从基类继承的方法");
// 获取所有方法
Method[] userClassDeclaredMethods = userClass.getDeclaredMethods();
for (Method userClassDeclaredMethod : userClassDeclaredMethods) {
System.out.println("修饰符:" + Modifier.toString(userClassDeclaredMethod.getModifiers()) + ",方法名称" + userClassDeclaredMethod.getName() +
",返回值:" + userClassDeclaredMethod.getAnnotatedReturnType().getType().getTypeName());
}
System.out.println();
System.out.println("getMethods() 根据名称和参数调用");
// 根据名称setUserName和参数类型String.class 调用赋值方法
Method userNameSetMethod = userClass.getMethod("setUserName", String.class);
userNameSetMethod.invoke(user,"马汇博");
// 根据名称 getUserName 调用获取值方法
Method userNameGetMethod = userClass.getMethod("getUserName");
Object getUserNameValue = userNameGetMethod.invoke(user);
System.out.println(getUserNameValue);
}
输出结果:
getMethods() 获取所有public定义的方法,包括继承下来的方法
修饰符:public,方法名称toString,返回值:java.lang.String
修饰符:public,方法名称getUserName,返回值:java.lang.String
修饰符:public,方法名称setPwd,返回值:void
修饰符:public,方法名称setUserName,返回值:void
修饰符:public,方法名称getPwd,返回值:java.lang.Integer
修饰符:public final,方法名称wait,返回值:void
修饰符:public final,方法名称wait,返回值:void
修饰符:public final native,方法名称wait,返回值:void
修饰符:public,方法名称equals,返回值:boolean
修饰符:public native,方法名称hashCode,返回值:int
修饰符:public final native,方法名称getClass,返回值:java.lang.Class<?>
修饰符:public final native,方法名称notify,返回值:void
修饰符:public final native,方法名称notifyAll,返回值:voidgetDeclaredMethods() 获取本身声明的方法,包括复写的方法,不包括从基类继承的方法
修饰符:public,方法名称toString,返回值:java.lang.String
修饰符:private,方法名称test,返回值:void
修饰符:public,方法名称getUserName,返回值:java.lang.String
修饰符:public,方法名称setPwd,返回值:void
修饰符:public,方法名称setUserName,返回值:void
修饰符:public,方法名称getPwd,返回值:java.lang.IntegergetMethods() 根据名称和参数调用
马汇博
获取方法的四个常用方法
1.getMethods()返回类中所有的public属性的方法,包括从基类继承的public方法。
2.getDeclaredMethods()返回类本身声明的方法,包括复写的方法,不包括从基类继承的方法
3.getMethod(name,params)根据参数从getMethods()返回的结果中筛选
4.getDeclaredMethod(name, params)根据参数从getDeclaredMethods()返回的结果中筛选
需要注意的是,invoke 第一个参数 是传入的对象,当前类的对象,第二个参数是参数类型,这个参数是可以多个的,动态改变的。
小马:明白了吗,鱼儿。反射看着很难,很麻烦,实际呢。
小鱼:我知道,实际很简单。
小马:不,实际用起来也很麻烦。一定要注意应用场景,应用的地方。否则就是降低性能,也没好的收益。虽然这次的不简单,但是也不是很难,回去多练练手吧。
小鱼:嗯。好的我知道了。
小马:在走之前,给你补一点框架利用反射的地方。
例如我们常用的Spring 框架中启动的时候会加载我们定义包范围的所有bean容器,并实例
首先先获取到包范围的所有class,在通过反射去寻找哪里使用了Spring 定义的组件注解,然后把找到的使用组件注解的类全部定义,然后通过反射的class.newInstance() 对类进行实例化,存入一个Map中,在判断需要注入的属性是否有set,如果有set使用invoke方法进行赋值,如果没有就修改权限进行赋值,然后Spring中的灵魂单例就是这样实现。
再或者Mybatis中 为什么我们可以直接写接口,不用写实现类,而查询结果又是怎么映射到类中的呢?使用了Java设计模式之一的动态代理模式(后面会讲到),然后重新实现了InvocationHandler 接口(代理拦截器),使用其中的方法invoke(),就是我们上面使用到的Method类中的invoke(),反射调用到jdk动态代理的方法。然后返回结果,重新经过解析,把结果返回之后,然后通过 resultMap 定义的 属性名和字段名进行反射调用,调用字段对应属性的set方法进行赋值。
小鱼:嗷嗷,原来反射在框架中那么强大啊,那我总结出几个优点和缺点
优点:
1.增加程序的灵活性,避免程序写死到代码中
2.代码会更简洁,封装起来提高重用率,外部调用方便
3.对于任何一个类,都可以知道它的所有方法和属性,对于任意一个类对象都可以去调用方法属性和赋值
缺点:
1.使用性能会造成性能下降
2.阅读会变困难许多
3.破坏封装性
小马:不错不错,总结的可以。那你下课吧。
小鱼:嗯嗯。
网友评论