参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448
Java 反射
在 Java 中,反射允许我们在运行时检查和操作类、接口、构造函数、方法和字段。程序在运行期可以拿到一个对象的所有信息。
Java中有一个类名为Class
,可以在运行时保存有关类和对象的所有信息。Class
的对象可用于执行反射。
除了int
等基本类型外,Java的其他类型全部都是class
(包括interface
)。例如:
String
Object
Runnable
Exception
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。
每加载一种class
,JVM就为其创建一个Class
类型的实例,并关联起来。
这里的Class
类型是一个名叫Class
的class
。它长这样:
public final class Class {
private Class() {}
}
以String
类为例,当JVM加载String
类时,它首先读取String.class
文件到内存,然后,为String
类创建一个Class
实例并关联起来:
Class cls = new Class(String);
这个Class
实例是JVM内部创建的,从上面的源码可以发现Class
类的构造方法是private,只有JVM能创建Class
实例,我们自己的Java程序是无法创建Class
实例的。
所以,JVM持有的每个Class实例都指向一个数据类型(class
或interface
):
一个Class
实例包含了该class
的所有完整信息:
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。
这种通过Class实例获取class信息的方法称为反射(Reflection)。
获取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
实例获取获取的基本信息,参考下面的代码:
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());
}
}
Class name: java.lang.String
Simple name: String
Package name: java.lang
is interface: false
is enum: false
is array: false
is primitive: false
Class name: java.lang.Runnable
Simple name: Runnable
Package name: java.lang
is interface: true
is enum: false
is array: false
is primitive: false
Class name: java.time.Month
Simple name: Month
Package name: java.time
is interface: false
is enum: true
is array: false
is primitive: false
Class name: [Ljava.lang.String;
Simple name: String[]
is interface: false
is enum: false
is array: true
is primitive: false
Class name: int
Simple name: int
is interface: false
is enum: false
is array: false
is primitive: true
数组(例如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()
被调用。
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}
static void create(String name) {
Person p = new Person(name);
}
}
当执行Main.java
时,由于用到了Main
,因此,JVM首先会把Main.class
加载到内存。然而,并不会加载Person.class
,除非程序执行到create()
方法,JVM发现需要加载Person
类时,才会首次加载Person.class
。如果没有执行create()
方法,那么Person.class
根本就不会被加载。
这就是JVM动态加载class的特性。
小结
JVM为每个加载的class
及interface
创建了对应的Class
实例来保存class
及interface
的所有信息;
获取一个class
对应的Class
实例后,就可以获取该class
的所有信息;
通过Class实例获取class信息的方法称为反射(Reflection);
JVM总是动态加载class
,可以在运行期根据条件来控制加载class
。