一、引入
在开始反射之前,我们先看看JVM是如何将我们写的类对应的java文件加载到内存中的。1、类的生命周期
这部分我们先讲讲JVM的加载机制(可能会有点难度,我尽力讲的直白点)我们写一个最简单的Main函数,来看看这个函数的是如何被执行的,代码如下:(Main.java)
- public class Main {
- public static void main(String[] args) {
- Animal animal = new Animal();
- animal.setName("cat");
- }
- public static class Animal{
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- }
大家都知道,在拿到一个java源文件后,如果要经过源码编译,要经过两个阶段:
编译:
- javac Main.java
运行
然后使用java Main命令运行程序:
- java Main
装载: 类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。 但是同一个类只会被类装载器装载一次,记住:只装载一次!
链接: 链接就是把二进制数据组装为可以运行的状态。链接分为校验,准备,解析这3个阶段。校验一般用来确认此二进制文件是否适合当前的JVM(版本),准备就是为静态成员分配内存空间,并设置默认值。解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)。
初始化: 初始化就是对类中的变量进行初始化值;完成之后,类型也就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用之后,将被垃圾回收。释放空间。
当没有任何引用指向Class对象时就会被卸载,结束类的生命周期。如果再次用到就再重新开始装载、链接和初始化的过程。
上面这一大段有关类生命周期有讲解,可能会有些难度,毕竟有关JVM的东东不是三言两语能讲透彻的,通过上面的这一段只想告诉大家一点: 类只会被装载一次!!!!利用装载的类可以实例化出各种不同的对象!
2、获取类类型
1、泛型隐藏填充类型默认填充为无界通配符?
在上面,我们讲了,类只会被装载一次,利用装载的类可以实例化出各种不同的对象。而反射就是通过获取装载的类来做出各种操作的。装载的类,我们称为类类型,利用装载的类产生的实例,我们称为类实例。下面我们就看看,如何利用代码获取类类型的:
- //使用方法一
- Class class1 = Animal.class;
- System.out.println(class1.getName());
- //使用方法二
- Class<?> class2= Animal.class;
- System.out.println(class2.getName());
从结果中可以看出class1和class2是完全一样的,那构造他们时的方法一和方法二有什么区别呢?
- //使用方法一
- Class class1 = Animal.class;
- //使用方法二
- Class<?> class2= Animal.class;
方法一中,是直接生成了一个Class的实例。
而在方法二中,则生成的是一个Class的泛型,并且使用的是无界通配符来填充的。有关无界通配符的事,下面再说,这里先讲讲方法一中直接生成Class对象与方法二中生成的Class泛型的区别。
我们都知道,Class类是一个泛型。而泛型的正规写法就应该是
- Class<Animal> class2= Animal.class;
- Class<?> class1 = Animal.class;
如果我们不用通配符,也就只能这样写:
- Class<Animal> class2= Animal.class;
在本文中,为了不误导大家,我们采用完整的Class填充方式即Class<?>
2、获取类类型的方法
上面我们通过Class<?> class1 = Animal.class,即直接使用类名的Class对象可以获取类类型,这只是其中一个方法,下面这四种方法都可以获得对应的类类型:
- //方法一:
- Person person = new Person();
- Class a = person.getClass()
- //方法二:
- Class b = Persion.class;
- //方法三:
- Class c = Class.forName(String ClassName);
- //方法四:(不建议使用)
- Class d = context.getClassLoader().loadClass(String ClassName);
方法二中,直接通过类的class对象得到
方法三和方法四中是通过类名得到,这两点要非常注意,这里的ClassName一定要从包名具体到类名,唯一定位到一个类才行,不然就会报ClassNotFound错误
在上面我们提到过,类只会被加载一次,所以a,b,c,d都是相等的,因为他们都是指向同一个对象,如果用等号操作符来判断的话:
- boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);
下面我们针对方法三和方法四举个粟子来看下:
先看一下完整的代码结构:
可以看到我们有一个Activity:MyActivity,和一个类Animal;
我们在Activity上放一个btn,把所有代码放在btn的点击响应中,Activity的布局难度不大就不再贴出代码,下面仅针对类类型的代码讲解。
Animal类的定义与上方一样,唯一不同的是,Animal类已经不再是内部类了而是单独出来的一个类。
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- //btn点击时调用demoFunc()函数
- Button button = (Button)findViewById(R.id.btn);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try{
- demoFunc();
- }catch (Exception e){
- System.out.print(e.getMessage());
- }
- }
- });
- public void demoFunc()throws Exception{
- Class<?> class1 = Class.forName("com.example.myReflect.Animal");
- Log.d(TAG,"通过Class.forName获得的类名:"+class1.getName());
- class1 = getClassLoader().loadClass("com.example.myReflect.Animal");
- Log.d(TAG,"通过ClassLoader获得的类名:"+class1.getName());
- }
- private static String TAG = "qijian";
从上面的用法中,可以看出,我们要使用Class.forName()或者getClassLoader().loadClass(),其中的类名必须是从包名到类名的完整路径!
从这里看来Class.forName()和getClassLoader().loadClass()是相同的,其实他们是有区别的,平时,我们不建议使用getClassLoader().loadClass()的方法来加载类类型。有关Class.forName()和getClassLoader().loadClass()的具体区别,会在本篇末尾讲述。
二、基本类类型周边信息获取
我们知道类分为基本类和泛型类,这篇我们只讲基本类类型的周边信息获取,有关泛型类的周边信息获取,我们会放到下一篇中。
这部分主要讲述类类型周边信息获取方法,包括类名,包名,超类和继承接口。
1、类名,包名获取
相关的有三个函数:- //获取完整的类名(包含包名)
- public String getName();
- //仅获取类名
- public String getSimpleName();
- //获取类类型所对应的package对象
- public Package getPackage()
- Class<?> class1 = Animal.class;
- Package package1 = class1.getPackage();
- Log.d(TAG,"完整的类名:"+class1.getName());
- Log.d(TAG,"仅获取类名:"+class1.getSimpleName());
- Log.d(TAG,"包名:"+package1.getName());
从结果中很清晰的看到,class.getName()获取的是类名包含完整路径。调用Class.forName()就是用的这个值。class.getSimpleName()得到的是仅仅是一个类名。而class.getPackage()得到的是该类对应的Package对象。通过package.getName()能获得该类所对应的包名。
有关Package类的相关函数就不讲了,基本用不到。
(2)、获取超类Class对象
获取superClass的类对象,涉及到两个函数:- //获取普通函数的父类Class对象
- public Class<?> getSuperclass();
- //针对泛型父类而设计
- public Type getGenericSuperclass();
我们仍然利用前面讲到的Animal类,然后在其上派生一个AnimalImpl子类:
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- public class AnimalImpl extends Animal {
- }
- Class<?> class2 = Class.forName("com.example.myReflect.AnimalImpl");
- Class<?> parentClass = class2.getSuperclass();
- Log.d(TAG, "父类:" + parentClass.getName());
在这里,我们使用了Class.forName(“com.example.myReflect.AnimalImpl”);找到AnimalImpl的类类型对象
然后调用 class2.getSuperclass()找到它的父类Class对象。很明显,它的父类是Animal类
由于我们这里得到了父类的Class对象parentClass,所以可以对它使用Class的一切函数。
所以调用parentClass.getName()就可以获得父类的名称了。
(3)、获取类所直接继承的接口的Class对象
这里要先声明一个观点:Class类,不同于定义类的class标识,Class类是一个泛型。类对象是由Class对象来表示,而接口对象同样也是用Class对象来表示!所以同样是Class对象,它可能表示的类对象,也可能表示的是接口对象!
获取接口对象的函数如下:
- //获取普通接口的方法
- public Class<?>[] getInterfaces();
- //获取泛型接口的方法
- public Type[] getGenericInterfaces();
getInterfaces()将获取指定类直接继承的接口列表!注意一点:直接继承!!!如果不是直接继承,那将是获取不到的。
我们举个例子:
同样,以上面的Animal为例:
我们先声明一个接口,让Animal类来继承:
- public interface IAnimal {
- void setName(String name);
- String getName();
- }
- public class Animal implements IAnimal{
- private String name;
- @Override
- public String getName() {
- return name;
- }
- @Override
- public void setName(String name) {
- this.name = name;
- }
- }
- public class AnimalImpl extends Animal {
- }
然后我们分别看看Animal类和AnimalImpl类的的获取接口的结果,完整的代码如下:
- //获取Animal类的接口列表
- Class<?> class3 = Animal.class;
- Class<?>[] interfaces = class3.getInterfaces();
- for (Class interItem:interfaces){
- Log.d(TAG, "Animal继承的接口:" + interItem.getName());
- }
- //获取AnimalImpl的接口列表
- class3 = AnimalImpl.class;
- interfaces = class3.getInterfaces();
- if (interfaces.length >0) {
- for (Class interItem : interfaces) {
- Log.d(TAG, "AnimalImpl继承的接口:" + interItem.getName());
- }
- }else {
- Log.d(TAG, "AnimalImpl无继承的接口");
- }
我们先看看Animal类的接口列表:
- Class<?> class3 = Animal.class;
- Class<?>[] interfaces = class3.getInterfaces();
- for (Class interItem:interfaces){
- Log.d(TAG, "Animal继承的接口:" + interItem.getName());
- }
从结果可以看出,这里找到了Animal类所继承的接口值。
那我们再来看看AnimalImpl的接口获取情况又是怎样呢:
- class3 = AnimalImpl.class;
- interfaces = class3.getInterfaces();
- if (interfaces.length >0) {
- for (Class interItem : interfaces) {
- Log.d(TAG, "AnimalImpl继承的接口:" + interItem.getName());
- }
- }else {
- Log.d(TAG, "AnimalImpl无继承的接口");
- }
从结果也可以看出,这里获取到的接口列表为空!所以这也证明了getInterfaces()只能获取类直接继承的接口列表。
(4)、综合提升:获取某个类类型的所有接口
现在我们提升一下,如果我想传进去一下类类型,然后要得到它所有继承的接口列表要怎么办?(不管它是不是直接继承来的都要列出来)那只有靠递规了,我们需要递规它的父类直接继承的接口、父类的父类直接继承的接口以此类推,最终到Object类的时候就找到所有继承的接口了
在开始递规获取所有接口之前,我们先构造下代码。
由于我们要获取所有接口,为了效果更好些,我们在Animal和AnimalImpl基础上,多加几个继承的接口:
- //给Animal添加 IAnimal,Serializable两个接口
- public class Animal implements IAnimal,Serializable{
- private String name;
- @Override
- public String getName() {
- return name;
- }
- @Override
- public void setName(String name) {
- this.name = name;
- }
- }
- //给AnimalImpl添加Serializable接口
- public class AnimalImpl extends Animal implements Serializable {
- }
好了,言规正转,看获取类类型所有接口列表的方法:
- /**
- * 获取所传类类型的所有继承的接口列表
- * @param clazz
- * @return
- */
- public Class<?>[] getAllInterface(Class<?> clazz){
- //获取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //递规调用getAllInterface获取超类的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- //返回值
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
- }
- //调用
- Class<?>[] clazzes = getAllInterface(AnimalImpl.class);
- SpannableStringBuilder builder = new SpannableStringBuilder();
- for (Class clazz:clazzes){
- builder.append(clazz.getName());
- builder.append(" ");
- }
- Log.d(TAG, "AnimalImpl继承的所有接口:"+ builder.toString());
这段代码最关键的地方在于getAllInterface(Class<?> clazz);我们来看看这个函数是如何递规得到所有接口的数组的。
- public Class<?>[] getAllInterface(Class<?> clazz){
- //获取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //递规调用getAllInterface获取超类的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- //返回值
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
- }
- //获取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //递规调用getAllInterface获取超类的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
有些同学不解了,getAllInterface(superClazz)这不是当前函数自己吗?是的,我们写getAllInterface(superClazz)是来干什么的,就是用来获取传进去的类的所有接口,所以我们把父类传进去,当然也能获得它父类的所有接口列表了。(有关递规的知识,可能是有些难度的,递规不是本文重点,不做详细介绍,有疑问的同学可以搜搜这方面文章补充下)
我们再重复一遍,我们的getAllInterface(Class<?> clazz)函数,会返回clazz对象的所有接口列表。现在我们得到了它自己直接继承的接口,也有它父类的所有接口列表。那么,把它们两个合并,就是所有的接口列表了。
所以下面是接口列表返回的代码:
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
有点难度的地方,可能是当两个都不为空的时候,合并时的代码:
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
然后通过System.arraycopy()函数,将它们两个复制到result数组中。
System.arraycopy()的定义及释义如下:
- /**
- * 将指定个数的元素从源数组src复制到目标数组dst中
- *
- * @param src :源数组
- * @param srcPos:源数组开始复制的item的索引,从0开始
- * @param dst:目标数组
- * @param dstPos:目标数组开始接收复制item的位置索引,从0开始
- * @param length:要复制的元素个数
- */
- public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
- Class<?>[] clazzes = getAllInterface(AnimalImpl.class);
- SpannableStringBuilder builder = new SpannableStringBuilder();
- for (Class clazz:clazzes){
- builder.append(clazz.getName());
- builder.append(" ");
- }
- Log.d(TAG, "AnimalImpl继承的所有接口:"+ builder.toString());
到这里,基本类的周边信息获取就结束了,下面我们来看看泛型类的周边信息获取要怎么来做。
(5)、获取类的访问修饰符
由于我们在定义类时,比如下面的内部类:- public static final class InnerClass{
- }
我们先看一个例子(我们以上面的内部类InnerClass为例):
- Class<?> clazz = getClassLoader().loadClass(InnerClass.class.getName());
- int modifiers = clazz.getModifiers();
- String retval = Modifier.toString(modifiers);
- boolean isFinal = Modifier.isFinal(modifiers);
- Log.d(TAG, "InnerClass的定义修饰符:" + retval);
- Log.d(TAG, "is Final:" + isFinal);
首先,在这部分代码中,我们又换了一种类加载方式,使用的是ClassLoader;
然后我们单独来看看这句:
- int modifiers = clazz.getModifiers();
另外Java开发人员单独提供了一个类来提取这个整型变量中各标识位的函数,这个类就是Modifier
Modifier中主要有以下几个方法:
- //根据整型变量来生成对应的修饰符字符串
- String Modifier.toString(int modifiers)
- //以下这些方法来检查特定的修饰符是否存在
- boolean Modifier.isAbstract(int modifiers)
- boolean Modifier.isFinal(int modifiers)
- boolean Modifier.isInterface(int modifiers)
- boolean Modifier.isNative(int modifiers)
- boolean Modifier.isPrivate(int modifiers)
- boolean Modifier.isProtected(int modifiers)
- boolean Modifier.isPublic(int modifiers)
- boolean Modifier.isStatic(int modifiers)
- boolean Modifier.isStrict(int modifiers)
- boolean Modifier.isSynchronized(int modifiers)
- boolean Modifier.isTransient(int modifiers)
- boolean Modifier.isVolatile(int modifiers)
- String Modifier.toString(int modifiers)
其它的就是一些isXXXX(int moifiers)的判断指定标识位的函数了,没什么难度
在例子中,我们使用了Modifier.isFinal(int modifiers)来判断是不是具有final修饰符,返回结果为true;
从Modifier类的isXXX()系列函数中,可以看到它的修饰符确实很多,但这些修饰符,有些类是根本用不到的,比如isNative()等,这是因为不光类会使用Modifier类来判断访问修饰符,接口,成员变量和成员函数所对应的类型也同样都是使用Modifier来判断访问修饰符的,这些我们后面在讲到的时候,都会说到!
(6)、获取接口的访问修饰符
从上面获取类的访问修饰符时,我们讲过,接口,类,函数都是通过Modifier类判断访问修饰符的,又因为类和接口类型全部都是用Class对象来标识,所以接口和类的获取访问修饰符的方式完全相同,下面就举一个简单的例子:- //定义一个类部接口
- public static interface InnerInteface{
- }
- //使用
- Class<?> clazz2 = InnerInteface.class;
- int modifiers = clazz2.getModifiers();
- String retval = Modifier.toString(modifiers);
- boolean isInteface = Modifier.isInterface(modifiers);
- Log.d(TAG, "InnerClass的定义修饰符:" + retval);
- Log.d(TAG, "isInteface:" + isInteface);
- Class<?> clazz2 = InnerInteface.class;
因为我们现在知道Class对象,不光代表类也可以代表接口。
下面有关Modifier的使用与第五部分获取类的修饰符是一样的,就不再细讲。
(7)Class.forName(String className)与ClassLoader.loadClass(String ClassName)的区别
我们通过源码来看看他们的区别:先看Class.forName:
- public static Class<?> forName(String className) throws ClassNotFoundException {
- return forName(className, true, VMStack.getCallingClassLoader());
- }
- public static Class<?> forName(String className, boolean initializeBoolean,
- ClassLoader classLoader) throws ClassNotFoundException {
- Class<?> result;
- try {
- result = classForName(className, initializeBoolean,
- classLoader);
- } catch (ClassNotFoundException e) {
- Throwable cause = e.getCause();
- if (cause instanceof ExceptionInInitializerError) {
- throw (ExceptionInInitializerError) cause;
- }
- throw e;
- }
- return result;
- }
其中:
- className:类名
- initializeBoolean:表示是否需要初始化;如果设为true,表示在加载以后,还会进入链接阶段
- classLoader:ClassLoader加载器
下面再来看看ClassLoader.loadClass()
- public Class<?> loadClass(String className) throws ClassNotFoundException {
- return loadClass(className, false);
- }
- protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- Class<?> clazz = findLoadedClass(className);
- if (clazz == null) {
- try {
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- // Don't want to see this.
- }
- if (clazz == null) {
- clazz = findClass(className);
- }
- }
- return clazz;
- }
通过代码也可以看出来ClassLoader的loadClass(String className)只是将类加载出来,并没有链接与初始化的步骤。所以这就是它们的区别
最后,我们总结一下,Class.forName(String className)不仅会将类加载进来,而且会对其进行初始化,而ClassLoader.loadClass(String ClassName)则只是将类加载进来,而没有对类进行初始化。一般来讲,他们两个是通用的,但如果你加载类依赖初始化值的话,那ClassLoader.loadClass(String ClassName)将不再适用。
举例来说:
在JDBC编程中,常看到这样的用法,Class.forName(“com.mysql.jdbc.Driver”),如果换成了getClass().getClassLoader().loadClass(“com.mysql.jdbc.Driver”),就不行。
为什么呢?打开com.mysql.jdbc.Driver的源代码看看,
- // Register ourselves with the DriverManager
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
好了,这篇就到这了,内容不太多,但比较复杂。最后我们再总结一下这篇文章所涉及到的几个函数:
- //获取类类型对象的几种方式:
- Person person = new Person();
- Class a = person.getClass() //方法一:
- Class b = Persion.class;//方法二:
- Class c = Class.forName(String ClassName); //方法三:
- Class d = context.getClassLoader().loadClass(String ClassName);//方法四:(不建议使用)
- //获取包名类名
- public String getName();//获取完整的类名(包含包名)
- public String getSimpleName();//仅获取类名
- public Package getPackage()//获取类类型所对应的package对象
- //获取超类Class对象
- public Class<?> getSuperclass();//获取普通函数的父类Class对象
- public Type getGenericSuperclass();//针对泛型父类而设计(下篇讲解)
- //获取接口Class对象
- public Class<?>[] getInterfaces();//获取普通接口的方法
- public Type[] getGenericInterfaces();//获取泛型接口的方法
- //类访问修饰符
- int modifiers = clazz.getModifiers();//获取类访问修饰符对应的int变量
- String Modifier.toString(int modifiers) //根据整型变量来生成对应的修饰符字符串
- boolean Modifier.isAbstract(int modifiers)//isXXX()系列函数用以检查特定的修饰符是否存在