Reflection反射——Class类
概述
在Java中,除了int等基本类型外,Java的其他类型全部都是class(包括interface)。例如:
String、Object、Runnable、Exception……
Java反射机制是Java语言的一个重要特性。在学习Java反射机制前,需要了解两个概念:编译器和运行期。
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在Java中也就是把Java代码编译成class文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成了文本进行操作。比如:检查语法错误。
运行期:是把编译后的文件交给计算机执行,直到程序允许结束。所谓运行期就把磁盘中的代码放到内存中执行起来。
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能过获取自身的信息。在Java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法或者为属性赋值。例如:在主流的ORM中框架的实现中,运用Java反射机制可以读取任意一个JavaBean的所有属性,或者给这些属性赋值。
Java反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
- 生成动态代理
Class类
要想知道一个类的属性和方法,必须要获取到该类的字节码文件对象。获取类的信息时,使用的就是Class类中的方法。所以先要获取到每一个字节码文件(.class)对应的Class类型的对象。
class(包括interface)的本质是数据类型(Type)。class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。
注意:这里的Class类型是一个名叫Class的class,定义如下:
// final声明不允许继承
public final class Class{// 私有的构造方法private Class(){}
}
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:
Class cls = new Class(String);
这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现class类的构造方法private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。
所以JVM持有的每个Class实例都指向一个数据类型(class或interface):
|---------------------------------|
| Class Instance | -------> String
|---------------------------------|
| name = "java.lang.String" |
|---------------------------------||---------------------------------|
| Class Instance | -------> Random
|---------------------------------|
| name = "java.util.Random" |
|---------------------------------||---------------------------------|
| Class Instance | -------> Runnable
|---------------------------------|
| name = "java.lang.Runnable" |
|---------------------------------|
一个Class实例包含了该class的所有完整信息
|---------------------------------|
| Class Instance | -------> String
|---------------------------------|
| name = "java.lang.String" |
|---------------------------------|
| package = "java.lang" |
|---------------------------------|
| super = "java.lang.Object" |
|---------------------------------|
| Interface = CharSequence ... |
|---------------------------------|
| field = value[] ,hash,... |
|---------------------------------|
| method = indexOf() ... |
|---------------------------------|
由于JVM每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段(成员变量)等,因此,如果获取了某个Class实例,我们就可以通过这个Clas实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。
如何获取一个class的Class实例?由三个方法:
方法一:直接通过一个class的静态变量class获取:
Class cls = String.class;
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");
因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例:
Class cls1 = String.classString s = "Hello";
Class cls2 = s.getClass();boolean sameClass = cls1 == cls2; // true
反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object实例时,我们可以通过反射获取该Object的class信息:
void printObjectInfo(Object obj) {Class cls = obj.getClass();
}
要从Class实例获取获取的基本信息,参考下面的代码:
public class Main {public static void main(String[] args) {printClassInfo("".getClass());printClassInfo(Runnable.class);printClassInfo(java.time.Month.class);printClassInfo(String[].class);printClassInfo(int.class);}static void printClassInfo(Class cls) {System.out.println("Class name: " + cls.getName());System.out.println("Simple name: " + cls.getSimpleName());if (cls.getPackage() != null) {System.out.println("Package name: " + cls.getPackage().getName());}System.out.println("is interface: " + cls.isInterface());System.out.println("is enum: " + cls.isEnum());System.out.println("is array: " + cls.isArray());System.out.println("is primitive: " + cls.isPrimitive());}
}
注意,数组(例如String[])也是一种类,而且不同于String.class,它的类名是java.lang.String。此外,JVM为每一种基本类型如int也创建了Class实例,通过int.class访问。
如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:
// 获取String的Class实例:
Class cls = String.class;// 创建一个String实例:
String s = (String) cls.newInstance();
上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。
Class常用方法
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 |
动态加载机制
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如
public class Main {static {System.out.println("Main被加载");}public static void main(String[] args) {int rand = new Random().nextInt(10);if (rand > 5) {create(rand);}}static void create(int no) {Person p = new Person(no);}
}class Person{static {System.out.println("Person类被加载");}public Person(int no) {System.out.println("Person类的有参构造方法");}
}
当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。
小结
- JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
- 获取一个class对应的Class实例后,就可以获取该class的所有信息;
- 通过Class实例获取class信息的方法称为反射(Reflection);
- JVM总是动态加载class,可以在运行期根据条件来控制加载class;