Java的动态性--反射

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34436819/article/details/53771327

反射可以说是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的结构自动创建表。

五、反射的应用

关于反射的应用,用到的地方实在是太多了,笔者就不细说了,读者可以去看看这篇文章(设计模式之代理模式),以及笔者后续的一篇文章(利用反射和注解模拟ORM框架中的自动建表功能

六、总结

关于反射的基本内容差不多就这么多了,当然,笔者在这里介绍的知识一点皮毛,不过足以入门反射了,想要学的更深,还得不断的读源码以及实战,希望读者也能继续加油,笔者也会继续深入理解反射以及更多关于类加载的过程。如果想走的远,个人认为就必须得把反射学得彻底,这样我们才能更好的理解框架,甚至尝试自己去写框架。


展开阅读全文

没有更多推荐了,返回首页