前言:坚持梦想,过程或是艰辛的,回忆是幸福的。与其最后豪言如果当时我怎样怎样,倒不如坚持脚下。

 

今天开始给大家讲讲有关反射的知识,在应用程序开发时,如果纯做上层,搭搭框架啥的,那用到反射的机会不多,但如果你想做出来一个公共类或者公共模块给其它人用的时候,那用到反射的可能性就大大增加了。况且反射听起来也是蛮屌的名字,今天就我们彻底认识他下吧。

 

一、引入

在开始反射之前,我们先看看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;
		}
	}
}
这段代码很简单,我们定义了一个Animal的类,在main()函数中,我们首先定义了一个Animal实例,然后调用了该实例的setName()方法。
大家都知道,在拿到一个java源文件后,如果要经过编译,要经过两个阶段:
编译:
javac Main.java
在执行后后在同一目录下生成Main.class和Animal类对应的文件Main$Animal.class(由于我们的Animal类是Main中的内部类,所以用$表示Main类中的内部类)
运行
然后使用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;
可以看到这两个方法,右边全部都是Animal.class,而左边却有些不同。
方法一中,是直接生成了一个Class的实例。
而在方法二中,则生成的是一个Class的泛型,并且使用的是无界通配符来填充的。有关无界通配符的事,下面再说,这里先讲讲方法一中直接生成Class对象与方法二中生成的Class泛型的区别。
我们都知道,Class类是一个泛型。而泛型的正规写法就应该是
Class中,常看到这样的用法,Class.forName(“com..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!);
    }
}
原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。

好了,这篇就到这了,内容不太多,但比较复杂。最后我们再总结一下这篇文章所涉及到的几个函数:
//获取类类型对象的几种方式:
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()系列函数用以检查特定的修饰符是否存在