反射可以说是Java基础当中相当重要的知识了,因为反射为Java语言实现了动态性,同时也为Spring、Struts2、Hibernate、Mybatis、SpringMVC等框架提供了实现的可能性。众所周知,Java不同于那些动态语言,如Javascript、Ruby、Python,Java是一门静态语言,但为了实现动态性,Java提供了一个包:java.lang.reflect,这个包为Java实现动态性提供了基础,即反射,所有与反射有关的类都处于该包下。
一、类的加载
要搞清楚反射机制,首先得知道在Java中类的加载机制。我们编写的.java文件首先被编译器编译成一个字节码文件(即.class文件),然后在程序运行时,虚拟机JVM的类加载器会将此字节码文件加载到内存的方法区中(一种特殊的堆),在该方法区中包含了类的运行时数据,同时还会在该方法区中生成一个对应的java.lang.Class对象,该对象时作为外部访问该对象的入口。(注意:一个类只会被虚拟机加载一次)
二、反射的核心对象--Class
java.lang.Class类时整个反射技术中最重要的对象,所有的其他反射对象都是通过Class类得到到,因此可以说Class是整个反射的源头。从上图中我们也可以看到,Class对象是外部访问该类对象的入口,在虚拟机加载完每一个类的同时都会对应生成一个相应的Class对象,它就像每个类的镜子一样,可以从该Class中获取到对应类的所有结构。既然Class是所有反射的源头,那该如何获取到该Class对象呢?
三、如何获取到Class对象
获得类所对应的Class对象有通常有四种方法,这里为了便于举例说明,就先定义了一个Student类:
package com.tiantang.reflection;
public class Student {
// 私有属性
private String name;
//公有的成员变量
public int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(String name) {
this.name = name;
}
}
下面通过获取Student类所对应的Class对象来举例说明,如何获取Class对象
(1)利用类名.class获得
例如:
(2)利用Class类中的静态方法Class.forName(str)
形参str指的是你所要获取Class所对应的类的全名
例如:
(3)利用对象.getClass()方法
每一个类都是Object的子类,而Object类中有一个getClass()方法,可以通过该方法来得到类所对应的Class对象
例如:先创建一个Student对象:
然后通过对象.getClass()方法得到Class对象:
(4)通过类加载器获得
在这里我们先创建一个TestClass类,我们在TestClass类中获得Student类所对应的Class对象
例如:先获取到类加载器:
然后利用类加载器的loadClass(str)方法获得:
loadClass()方法中的形参str指的是你所要获取Class所对应的类的全名。
以上通过四种方式分别获得了Student类所对应的Class对象(clazz,clazz2,clazz3,clazz4),由于虚拟机对每个类只会加载一次,因此这四个Class对象时同一个对象,读者可以分别打印一下这四个对象的hash值。
四、通过Class对象来获得Student类的完整结构
Class类中提供了相应的方法来供我们获取到类的完整结构,例如获得类的属性、方法、构造器、注解、类名、类全名、权限修饰符、方法的返回值类型等等。
(1)获得类的属性
Class类中提供了四个方法,都可以来获得类的属性。
①:Field:getField(String name):通过属性名来获取属性,返回的是一个Field类型的对象
②:Field[]:getFields():该方法获得的是本类以及父类的所有权限为public的属性对象,返回值是一个Field类型的数组
③:Field:getDeclaredField(String name):通过属性名获取本类中定义的属性(自己定义的属性,不论是public还是private),返回值是一个Field对象。
④:Fields[]:getDeclaredFields()::获取本类中所有自己定义的属性(自己定义的属性,不论是public还是private),返回值是一个Field类型的数组。
通过以上四个方法,可以获取到类中的属性,这四个方法大致可以分为两组,要注意其中的区别。
Field对象:从Field对象中我们可义得到属性名(getName()),属性的权限修饰符(getModifiers(),该方法返回的是一个int型的值,如果要将其变为public,private,protected等字符串,则需要用如下方法:Modifier.toString(int)),同时还可以获得属性的值,给属性赋值,属性的注解等等,Field提供了一些列对属性的操作的方法。读者可以阅读jdk的源码或者相关的API去了解。
(2)获得类的方法
和获得属性类似,Class类也提供了四个方法来获得类的方法。
①:Method:getMethod(String name):根据方法名获得方法,返回的是Method对象;
②:Method[]:getMethods:返回的是本类及父类中所有权限为public的方法所组成的数组;
③:Method:getDeclaredMethod(String name):根据方法名获得本类中自己定义的方法;
④:Method[]:getDeclaredMethods:获得本类中自己定义的方法所组成的数组。
同样这四个方法可以分为两组,在使用时应该注意他们的区别。
Method对象:该对象表示的是方法所对应的类,通过该对象,可以得到方法的注解,方法名,修饰符,返回值类型,以及调用invoke方法来调用对象中的方法。读者可以阅读jdk的源码或者相关的API去了解。
(3)获得类的构造器
与(1)(2)类似,同样是四个方法,建议读者多去看看源码,笔者就不一一列举了,因为这与上面都是重复的,读者只要理解了其中之一,就能明白其他的。
(4)获取父类以及实现的接口
①:获得父类:getSuperclass();
②:获得带泛型化的父类:getGenericSuperclass();
③:获得实现的接口:getInterfaces()以及getGenericInterfaces()
这里笔者重点解释一下父类和泛型化的父类,以及如何获取父类的泛型化参数
我们先定义一个People类,让People类带泛型:
然后再定义一个Student类,并让Student实现People类,并让泛型具体化:
测试代码:
打印结果:
从结果中发现:getSuperclass()获得的是不带泛型的父类,而getGenericSuperclass()得到父类带上了泛型,这就是两者的区别。
那什么是获得父类的泛型化参数呢?
从上面的例子中,我们知道在Student继承People是,让泛型T具体化了,即具体泛型是String,获得父类的泛型化参数就是获得该String类对象。
那应该如何获得呢?
这段代码十分重要,以后会经常用到,用到的最多指出就是在JDBC中,Dao具体操作哪一类对象时就必须通过反射获得,不过这段代码基本是套路,读者如果无法理解的话,也可以收藏下来,多敲几次。
(5)获得注解
Class类中提供了一些方法来获取类上的注解,如:getAnnotation(Class)和getAnnotations(),前者返回的是Annotation对象,或者返回的是Annotation[]数组。得到Annotation对象后,读者就可以得到注解的具体信息。笔者会在下一篇博客(文章名:利用反射和注解模拟ORM框架中的自动建表功能)里写一个利用反射获得注解,然后拼接sql语句的练习,有点类似ORM框架里的通过反射和JavaBean的结构自动创建表。
五、反射的应用
六、总结
关于反射的基本内容差不多就这么多了,当然,笔者在这里介绍的知识一点皮毛,不过足以入门反射了,想要学的更深,还得不断的读源码以及实战,希望读者也能继续加油,笔者也会继续深入理解反射以及更多关于类加载的过程。如果想走的远,个人认为就必须得把反射学得彻底,这样我们才能更好的理解框架,甚至尝试自己去写框架。