绪论
首先,什么是反射机制?
它是一种 “运行时检视类型信息”、“修改属性”、“调用方法”的一种机制,
其次,常用的运用场景有哪些?
运行时实例化类的对象。(例如:实例化一个 User 类,此 User 类的名字容器事先是不知道的)
运行时调用方法。(例如:调用 User 对象的 a 方法)
运行时修改属性值。(例如:修改 User 对象的 name 属性)
最后,温馨提示
JDK 中关于反射的相关类型都在 “java.lang.reflect” 包下,并不需要额外的第三方包来完成反射。
第一章:获取 Class对象
1、啥是 Class对象?
在 java 中,用来表示运行时类型信息的对应类就是 Class 类,而由此 Class 类创建出来的实例就是传说中的 “Class 对象”。
2、咋获取 Class 对象?
有三种方法,咱细品:
第一种方法:Class.forName("类的全称")
此方法适合,只知道类的字符表示(人话:类的全称)的情况。若类还没加载此类,则加载;若类已经加载了此类,则返回已加载的类的 “Class 对象” 给你。
//like this
1 Class user = Class.forName("com.User");
第二种方法:类的 Class字段
此方法适合,容器事先就知道该类的情况。
1 //比如:
2 Class user1 = User.class;
4
5 //也可以这样:
6 Class integerCls = Integer.class;7
8 //对于8个基本类型而言,想获取其 Class对象的信息,
除了用 class字段的方式外,还可以用其对应的包装类型的 TYPE字段来获取,像这样:
9 Class intCls = int.class;10 Class intCls1 = Integer.TYPE;
第三种方法:对象的 getClass() 方法
此方法适合,已有该类的 “Class 对象”的情况。
1 //like this
2 User user = newUser();3
4 Class userCls = user.getClass();
//就是直接利用已知的类的对象来获取该类的 Class对象信息
第二章:Class对象的基本操作
咱有了 Class对象后,可以干的各种坏事如下:
1、获取名字
1 //获取某个类的Class对象
2 Class clazz=...;3
4 //通过反射获取此类的简称
5 String simpleName =clazz.getSimpleName();6
7 //通过反射花去此类的全称
8 String fullQualifiedName = clazz.getName();
2、获取修饰符
通过 Class对象的 getModifiers() 方法获取此类的修饰符,但是此方法返回的是一个整数,此整数的含义即代表是什么修饰符,咱还需靠 Modifier类的一系列方法来分析,例如:
//获取到代表类修饰符的整数(此时我们不知道这个整数啥意思)
int modifier =clazz.getModifiers();//判断是否是public
boolean isPublic =Modifier.isPublic(modifier);//判断是否是Abstract
boolean isAbs = Modifier.isAbstract(modifier);
.
.
.
3、获取所在包的信息
//通过反射获取包
Package pkg =clazz.getPackage();//通过包对象获取包信息
String pkgName = pkg.getName();
4、获取父类信息
靠 getSuperClass()方法实现,若当前 Class对象代表的是 Object类型、接口类型、void类型、基本类型,则此方法返回null值
//通过反射获取父类对象
Class> superClazz =clazz.getSuperClass();//靠父类对象来获取父类的简称
String superClassName = superClazz.getSimpleName();
5、获取实现的接口
通过 getInterfaces() 方法实现
//省去获取 Class对象 的步骤
Class>[] personInterfaces = clazz.getInterfaces();
分下面几种情况:
第一种情况:当前的 Class对象代表的是一个类
此方法得到此类声明实现的所有接口信息,并不包含其父类所实现的接口,返回的数组中,按声明顺序排序;若没有,则返回长度为0的数组。
第二种情况:当前的 Class对象代表的是一个接口
此方法返回的是,此接口extends(继承)的所有接口信息,数组中顺序按照继承的接口顺序排序;若没有,则返回长度为0的数组。
第三种情况:当前的 Class对象代表的是void或基本类型
此方法返回长度为0的数组。
第四种情况:当前的 Class对象代表的是数组类型
此方法返回的是Cloneable 和 Seializable。
6、构造函数
(1)、获取所有构造函数
//通过反射获取所有构造函数
Constructor>[] constructors = clazz.getConstructors();
(2)、获取某个构造函数
//根据参数类型指定获取某个构造函数,若没有对应的构造函数则会抛出异常
Constructor>[] cons1 = clazz.getConstructor(String.class);
Constructor>[] cons2 = clazz.getConstructor(Integer.class);//有了构造函数之后,就可以创建实例了(上文提到的没有无参构造时的情况)
cons1.newInstance("hello");
cons2.newInstance(2);
7、字段
字段分为该类自己声明(Declare)的和从父类继承过来的。
下面的返回数组的方法中,数组中元素是没有进行排序的,所以你的代码不能依赖反射中得到的字段顺序来编写逻辑。获取字段对象:
//通过反射获取自身和从父类继承过来的所有 public 修饰的字段
File[] filds =clazz.getFilelds();//通过反射获取自己声明的(不包含从父类继承过来的)
File[] filds =clazz.getDeclareFields();//通过反射得到具体名字的字段
File f = clazz.getDeclareFields("字段名");
注意:如果 Class对象代表的类型没有字段,或者代表的是数组类型/基本数据类型/void类型,都会返回一个长度为0的数组。
得到字段对象后,我们可以做相关操作:
Field a = clazz.getDclaredField("a");//当不可访问时,对字段的可访问性进行调整方可获得字段值
a.setAccessible(true);
sout(a.get(user));
a.setAccessible(false);
获取字段值,主要靠 get() 方法来完成,如若确定字段的类型,则可调用对应类型的方法(例如:调用getInt() 方法获取证书字段的值),注意:此种以get开头获取字段值的方法主要针对八个基本类型
众所周知,字段分为静态字段和实例字段,静态字段的调用不需要实例,如下:
//从头开始,获取Class对象
Class clazz = User.class;
User user=clazz.newInstance();//获取静态字段值 (假设name是一个public static字段)
Field name = clazz.getDeclaredField("name");//输出静态字段值,要传入null,此时不确定字段类型
sout(name.get(null));//获取实例字段值
Field sex = clazz.getDeclaredField("sex");//要传入该类的实例,此时不确定字段类型
sout(sex.get(user));
设置字段值
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(user,"张三");//设置字段值
sout(name.get(user));//输出字段值
name.setAccessible(false);
8、方法
方法也分为自身的和继承过来的。下面是获取方法:
//获取自身声明的方法
Method[] mehtods =clazz.getDeclareMethods();//获取所有的方法(不管是自身还是继承)
Method[] methods =clazz.getMethods();//由于方法有重载功能,所以我们有时需要制定哪一个方法
Method method = clazz.getDeclareMothods("doSth",String.class);//doSth 表示方法名;String.class 表示参数类型
对获取到的 Method对象,我们可以对它进行一些操作:
//获取可访问性
boolean access =method.isAccessible();//得到方法名字
String name =method.getName();//得到方法参数个数
int parameterCount =method.getParameterCount();//得到所有参数信息
Parameter[] ps = method.getParameters();
调用方法
//假设类中有如下的方法
public voiddoSth(String name){
sout(name+"doSth");
}//则下面是通过反射调用其方法
Class clazz = User.class;//获取Class对象
User user = clazz.newInstance();//通过反射获取类实例
Method m = clazz.getDeclaredMethod("doSth", String.class);//得到指定方法,指定的条件是方法名和参数类型//调用,若此时方法不能访问,则需调整可访问性(开完后要及时关闭)
m.setAccessible(true);
m.invoke(user,"hello");
m.setAccessible(false);
9、实例化对象
我们得到 Class对象后,可通过此Class对象创建代表类的实例。
Class clazz =....;
Person p= clazz.newInstance();
注意:此方法要求类中必须有一个“无参构造函数”,如若没有,则此方法会抛出异常。
那么,没有无参构造函数我们怎么获得实例化对象呢?肯定是天无绝人之路,我们可以靠反射,先获取Constructor对象,也就是构造函数,再通过 Constructor对象来创建实例。
10、判断实例是否是某个类型(可能表达的不好)
Class对象还有一个 isInstance()方法,作用等价于 instanceOf操作符,用法如下:
User user = newUser();//通过类实例来得到 Class对象
Class clazz = User.class;//下面的输出是 true,因为user实例的类型是User类型
Sout(clazz.isInstace(user))
第三章:数组与反射
数组是某个类型对象的一个合集,基于这个认识,数组的反射与普通的反射有点不同,例如:
//创建一个长度为10的字符串数组//首先,获取到String类的Class对象
Class clz = Class.forName("java.lang.String");//其次,创建字符串数组
Object arr = Array.newInstance(clz,10);//设置第三个位置的字符串值
Array.set(arr,3,"hello world!");
sout((String)Array.get(arr,3));//输出第三个位置的值
String[] arr2=(String[])arr;
sout(arr2[3]);//同样是输出第三个位置的值
其中,Array类是在 java.lang.reflection包下面的类型。
本文仅供参考使用,如有不足之处请指出,谢谢。
参考资料:https://www.oracle.com/technetwork/articles/java/ javareflection-1536171.html