封面来源:本文封面来源于网络,如有侵权,请联系删除。

本文 JDK 版本为 JDK 17

1. 注解

1.1 自定义注解

使用 @interface 自定义注解,就会自动继承 java.lang.annotation.Annotation 接口。

@interface 可以用来声明一个注解,格式:public @interface 注解名 {定义内容}。注解中每一个方法表示声明了一个配置参数,而方法的名称就是参数的名称,返回值类型就是参数的类型(返回值只能是基本类型、Class、String 和 enum),并且可以通过 default 关键词来声明参数的默认值。如果只有一个参数成员,一般参数名为 value。

注解元素必须要有值。定义注解元素时,经常使用空字符串或 0 作为默认值。

1.2 元注解

元注解的作用就是负责注解其他注解,Java 定义了 4 个标准的元注解(meta-annotation)类型,它们被用来提供对其它注解(annotation)类型作说明。

这四个元注解位于 java.lang.annotation 包中,它们分别是:

1
2
3
4
@Target // 用于描述注解的使用范围
@Retention // 表示需要在什么级别保存该注解信息,用于表述注解的生命周期。SOURCE < CLASS < RUNTIME
@Documented // 说明该注解将被包含在 javadoc 中
@Inherited // 说明子类可以继承父类中的该注解

2. 类的加载

2.1 类的加载

类的加载

将 Class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象。

类的连接

类的连接是指将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。主要分为三步,分别是验证、准备和解析。

  1. 验证:确保加载的类信息符合 JVM 规范,即加载的 Class 文件的格式是否正确。
  2. 准备:正式为类的静态变量(static)分配内存并为其设置默认初始值的阶段。
  3. 解析:虚拟机常量池内的符号引用(变量名)替换为直接引用(地址)的过程。

类的初始化

类的初始化就是执行类构造器 <clinit>() 方法的过程。类构造器 <clinit>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)。

当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确加锁和同步。

2.2 类初始化

类的主动引用一定会发生类的初始化。 比如:

  1. 当虚拟机启动时,初始化 main() 方法所在的类;
  2. new 一个对象;
  3. 调用类的静态成员(final 常量除外)和静态方法;
  4. 使用 java.lang.reflect 包中的方法对类进行反射调用;
  5. 初始化一个类时,如果其父类没有被初始化,则会先初始化它的父类。

类的被动引用不会发生类的初始化。 比如:

  1. 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化;
  2. 通过数组定义类引用,不会触发此类的初始化;
  3. 引用常量不会触发其所在类的初始化(常量在连接阶段就存入调用类的常量池中了)。

2.3 类加载器

类加载器

类加载器(ClassLoader)的作用:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。

简单来说,类的加载阶段有这样一个动作:通过一个类的全限定名来获取描述此类的二进制字节流。这个动作放到了 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类,而实现这个动作的代码模块就叫做类加载器。

类加载器在 Java 程序中起到的作用不局限于类加载阶段。为了确定任意一个类在 JVM 中的唯一性,除了这个类本身以外,还需要加上加载这个类的类加载器作为依据。每一个类加载器,都拥有一个独立的类命名空间。

也就是说,要判断两个类是否相等,只有在这两个类都是由同一个类加载器加载的前提下才有意义。因此就算是两个来自于同一 class 文件,被同一个虚拟机加载的类,但加载它们的类加载器不同,那也不能认为这两个类相等。这里的“相等”不仅仅指使用 equals() 方法比较,还包括 isAssignableFrom() 方法和 isInstance() 方法的返回结果,以及使用 instanceof 关键字进行判断。


拓展:Class#isAssignableFrom() 方法与 instanceof 关键词的使用。

Class#isAssignableFrom() 方法用于判断某个类是否是另一个类的父类,instanceof 关键词用于判断某个实例是否是某个父类类型。

使用方式:

1
2
3
父类.class.isAssignableFrom(子类.class)

子类实例 instanceof 父类类型

类加载器的种类

1、启动类加载器(Bootstrap ClassLoader),或者说引导类加载器、根加载器。这个类加载器由 C++ 编写,是 JVM 自带的类加载器(是 JVM 的一部分)。

2、其他的类加载器。这些类加载器由 Java 语言实现,不在 JVM 中,并且都继承自 java.lang.ClassLoader

几个系统级别的类加载器

  • 启动类加载器(类加载器的种类中的第一种):这个类加载器负责将存放在 ${JAVA_HONE}\lib 目录中,或者被 -XbootstrapPath 参数所指定的 目录中,并且是虚拟机 基于一定规则(如文件名称规则,如 rt.jar)标识的 类库 加载到虚拟机内存中。该加载器无法通过 Java 程序直接获取,如果想委派到启动类加载器只需直接使用 null 替代即可(后面会说)。
  • 扩展类加载器(Extension ClassLoader):该类加载器由 sun.misc.Launcher 的静态嵌套类 ExtClassLoader 实现。它负责将 ${JAVA_HONE}\lib\ext 目录下或通过 -Djava.ext.dirs 参数指定的目录下的所有类库装入工作库。开发者可以直接使用此加载器。Java 9 移除了拓展机制,ExtClassLoader 被 PlatformClassLoader 取代,PlatformClassLoader 主要用于加载 Java 平台模块中的类,包括 java.sqljava.xml 中的类。
  • 应用程序类加载器(Application ClassLoader):该类加载器由 sun.misc.Launcher 的静态嵌套类 AppClassLoader 实现,由于该类加载器的实例是 ClassLoader 中静态方法 getSystemClassLoader() 中的返回值,因此这个类加载器也被成为 系统类加载器。它负责加载用户类路径(ClassPath)或 -Djava.class.path 所指的目录下的所有类库装入工作库,是最常用的加载器。开发者也可以直接使用此加载器。如果程序中没有自定义类加载器,一般情况下该类加载器就是程序中默认使用的类加载器。
  • 线程上下文类加载器(Thread Context ClassLoader):后面再说。 😉

2.4 类加载器的工作流程

我们编写的 Java 程序都是由上面四种类加载器相互配合进行类加载的,当然还可以自定义类加载器。其中,启动类加载器、拓展类加载器、系统类加载器和自定义类加载器的关系如下:

加载器工作流程

像上图这样的层次关系被称为 双亲委派模型,或者说双亲委派机制。除了顶层的启动类加载器,其他的类加载器都应当有自己的父类加载器。类加载器与其父类加载器之间的关系不会以继承(Inheritance)来实现,而是以组合(Composition):

1
2
3
4
5
6
7
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
// ...
}

简单验证下类加载器的层次关系:

1
2
3
4
5
6
7
@Test
public void test() {
ClassLoader classLoader = Person.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}

运行结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5caf905d
null    

结果符合我们的预期,而最后打印出的 null 说明了 classLoader.getParent() 指的是启动类加载器,因为它没有父类加载器。

那双亲委派模型的工作机制是怎样的呢?

当一个类加载器收到了类加载请求时,它不会自己尝试去加载这个类,而是把这个请求委派给它的父类加载器,直到请求传递到顶层的启动类加载器。如果父类加载器无法完成当前的类加载请求(在它的搜索范围内没有找到需要加载的类),那么父类加载器又会把类加载请求委派给它的子类加载器。当然也可能直到最后,这个类加载请求也没法完成,这时就会抛出 ClassNotFoundException 异常。

这里还需要引出 类缓存 的概念。所谓类缓存,就是“标准的 JavaSE 类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象”。

简单来说,类加载器会缓存自己已经加载过的类。当加载一个类时,首先会从缓存中加载,如果缓存中不存在,再按照前面所说的工作机制去加载类。

双亲委派模型的好处

使用双亲委派模型来协调类加载器之间的关系,可以使 Java 类随着加载它的类加载器一起具备一种带有优先级的层次关系。例如存放在 rt.java 中的 java.lang 包中的类,无论是哪个类加载器加载它们,最终都将由顶层的启动类加载器进行加载。

也正因如此,java.lang 包下的类在程序的各个类加载器中被加载时都是相等的(来自同一个 class 文件且被同一个类加载器加载)。

如果不这样,例如 java.lang 包下的 java.lang,Object 类被不同的类加载器加载时将不会相等,那么程序中就会出现多个 java.lang.Object 类。由于其他类都会隐式继承 java.lang.Object 类,当存在多个 Object 类时,程序将变得混乱。

双亲委派模型的实现

类加载器双亲委派模型的具体实现体现在 java.lang.ClassLoader 中,而其中的 loadClass() 方法则最能体现这个机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 先检查类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) { // 如果没被加载
long t0 = System.nanoTime();
try {
// 有父类加载器时,使用父类加载器进行加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 没有父类加载器时,证明父类加载器是启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 父类加载器加载失败时,使用自身的 findClass 方法进行加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// 一些耗时、计数等统计
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

2.5 破坏双亲委派模型

破坏双亲委派模型,或者说打破双亲委派机制,是什么意思?

在加载类时,如果不按照“自底向上检查是否已加载类,自顶向下尝试加载类”的方式去加载类,那么就可以叫做破坏双亲委派模型。

已经知道加载类的核心方法是抽象类 ClassLoader 中的 loadClass() 方法,可以通过继承这个抽象类并重写 loadClass() 方法,而且不按照双亲委派模型的方式去加载类,那么就可以打破双亲委派模型。

在 Java 发展史上也有双亲委派模型被破坏的情况,比如:

  1. 由于 java.lang.ClassLoader 在 JDK1.0 中已经存在,所以会有人继承这个抽象类并重写 loadClass() 方法来实现自定义类加载器。为了在 JDK1.2 中引入双亲委派模型并向前兼容,loadClass() 方法必须要保留并且能够被重写,于是在 ClassLoader 类添加了一个新的被 protected 修饰的 findClass() 方法,并告知开发者不要重写 loadClass() 而是重写 findClass()。由于双亲委派模型的具体实现就在 loadClass() 方法内,并未禁止重写这个方法,因此委派的逻辑就被破坏了。
  2. 双亲委派模型存在缺陷:双亲委派模型很好地解决了各个类加载器加载基础类的统一问题(越基础的类由越上层的类加载器加载),这些基础类大多数情况下作为用户调用的基础类库,但 这些基础类无法回调用户的代码。以 JDBC 为例,它规定了如何使用 Java 代码来连接数据库,具体的做法需要交由各个数据库厂商实现。JDBC 位于 rt.jar 中,由启动类加载器去加载,但其具体实现是在用户定义的 ClassPath 中,只能由应用类加载器去加载,因此启动类加载器只能委托子类加载器去加载数据库厂商们的具体实现,而这就破坏了双亲委派模型。具体实现方式是引入了线程上下文类加载器(Thread Context ClassLoader),可以通过 java.lang.ThreadsetContextClassLoader() 方法来设置,然后利用 Thread.current.currentThread().getContextClassLoader() 获得类加载器来加载(如果直接获取,将获取到应用程序类加载器)。
  3. 用户对应用程序动态性的热切追求:如代码热替换(HotSwap)、热模块部署等,因此催生出 JSR-291 以及它的业界实现 OSGi,而 OSGi 定制了自己的类加载规则,利用自定义类加载器机制来完成模块化热部署,不再遵循双亲委派模型。

3. 反射

3.1 几个相关的类

  • java.lang.Class,代表一个类。
  • java.lang.reflect.Method,代表类的方法。
  • java.lang.reflect.Field,代表类的成员变量。
  • java.lang.reflect.Constructor,代表类的构造器。

3.2 Class 类

通过类的 Class 类可以知道这个类的:

  • 属性;
  • 方法和构造器;
  • 实现了哪些接口。

对于每个类而言,JRE 都为其保留了一个不变的 Class 属性的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。

需要注意的是:

  • Class 类本身也是一个类;
  • Class 对象只能由系统建立对象;
  • 一个加载的类在 JVM 中只会有一个 Class 实例;
  • 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件;
  • 可以通过任意一个类的实例获取到这个类的 Class 类;
  • 通过 Class 对象可以完整地得到某一个类中所有被加载的结构;
  • Class 类是反射的根源,要想进行反射,得先获取到对应的 Class 对象。

3.3 如何获取 Class 类的实例

若已知具体的类,通过类的 class 属性获取。该方法最为可靠,程序性能最高。

1
Class clazz = Person.class;

已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。

1
Class clazz = person.getClass();

已知一个类的全限定名,且该类在类路径下,可通过 Class 类的静态方法获取 forName() 获取。这一方法可能抛出 ClassNotFoundException 异常。

1
Class clazz = Class.forName("com.yang.reflect.Person");

内置基本数据类型可以直接用 类名.Type

1
Class<Integer> type = Integer.TYPE;

还可以利用 ClassLoader

1
2
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clazz = classLoader.loadClass("indi.mofan.domain.Person");

拥有 Class 对象的类型:外部类、成员(成员内部类、静态嵌套类)、局部内部类、匿名内部类、接口、数组、枚举、注解、基本数据类型,void。

3.4 使用反射获取对象的信息

给定一个 Person 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.yang.reflect;

public class Person {
private int id;
private String name;
private int age;

public Person(){}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

public int getId() {return id;}

public void setId(int id) {this.id = id;}

public String getName() {return name;}

public void setName(String name) {this.name = name;}

public int getAge() {return age;}

public void setAge(int age) {this.age = age;}

@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

利用反射创建 Person 对象,并调用该对象的方法、修改该对象成员变量的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Test01 {
public static void main(String[] args) throws Exception {
// 获得class对象
Class c1 = Class.forName("com.yang.reflect.Person");

// 构造一个对象
// Person person = (Person) c1.newInstance();
// System.out.println(person);

// 通过构造器创建对象
// Constructor constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);
// Person person2 = (Person) constructor.newInstance(1,"Yang",18);
// System.out.println(person2);

// 通过反射调用普通方法
Person person3 = (Person) c1.newInstance();
// 通过反射获取方法
Method setName = c1.getDeclaredMethod("setName", String.class);
// invoke: 激活
//(对象 , “方法的值”)
setName.invoke(person3,"mofan");
System.out.println(person3.getName());

// 通过反射操作属性
Person person4 = (Person) c1.newInstance();
Field name = c1.getDeclaredField("name");
// 不能直接操作私有属性,需要关闭程序的安全检测,
// 使属性或者方法的setAccessible为true,可提高执行效率
if (!name.isAccessible()) {
name.setAccessible(true);
}
name.set(person4,"Yang_2");
System.out.println(person4.getName());
}
}

利用 isAccessible() 方法可以判断 是否关闭 Java 语言访问控制的检查,关闭后才能操作私有属性。当未关闭安全检测时,可以使用 setAccessible() 方法并传入 true 表示关闭检查。

在 JDK 9 之后 isAccessible() 方法被废弃,废弃原因是它的方法名称不够准确,会让人觉得此方法用于检查反射的对象是否可访问,而实际上并非如此,作为代替,引入了 canAccess() 方法。

canAccess() 方法可以接收一个 Object 类型的参数,如果判断的是一个实例对象的方法或字段,应当传入此实例对象,反之传入 null 即可。

3.5 使用反射获取注解信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.yang.reflect;

import java.lang.annotation.*;
import java.lang.reflect.Field;

public class Test02 {

public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.yang.reflect.Student");

// 通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}

// 获得注解的 value 的值
Tableyang tableyang = (Tableyang) c1.getAnnotation(Tableyang.class);
String value = tableyang.value();
System.out.println(value);

// 获取类指定的注解
Field name = c1.getDeclaredField("name");
Fieldyang annotation = name.getAnnotation(Fieldyang.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}

@Tableyang("db_student")
class Student{
@Fieldyang(columnName = "db_id",type = "int",length = 10)
private int id;
@Fieldyang(columnName = "db_age",type = "int",length = 2)
private int age;
@Fieldyang(columnName = "db_name",type = "varchar",length = 8)
private String name;

public Student() {}

public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}

public int getId() {return id;}

public void setId(int id) {this.id = id;}

public int getAge() {return age;}

public void setAge(int age) {this.age = age;}

public String getName() {return name;}

public void setName(String name) {this.name = name;}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tableyang{
String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldyang{
String columnName();
String type();
int length();
}

运行结果如下:

1
2
3
4
5
@com.yang.reflect.Tableyang(value=db_student)
db_student
db_name
varchar
8

3.6 Class 的各种 name

参考链接:

Class 中有 getName()getCanonicalName()getSimpleName() 以及 JDK8 中新增的 getTypeName() 方法。

getName()

getName() 返回的信息可以在动态加载某个类时使用。比如使用默认的 ClassLoader 调用 Class.forName() 方法来加载某个类。在某个 ClassLoader 的范围内,所有类 getName() 返回的信息唯一。

1
2
3
4
5
6
7
8
9
public String getName() {
String name = this.name;
return name != null ? name : initClassName();
}

// Cache the name to reduce the number of calls into the VM.
// This field would be set by VM itself during initClassName call.
private transient String name;
private native String initClassName();

getCanonicalName()

getCanonicalName() 返回的信息可以在 import 语句中使用,也能在 toString() 方法或日志操作时使用。 注意: 在一个 ClassLoader 中,getCanonicalName() 返回的信息并不能用来唯一标识一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public String getCanonicalName() {
ReflectionData<T> rd = reflectionData();
String canonicalName = rd.canonicalName;
if (canonicalName == null) {
rd.canonicalName = canonicalName = getCanonicalName0();
}
return canonicalName == ReflectionData.NULL_SENTINEL? null : canonicalName;
}

// 核心实现
private String getCanonicalName0() {
if (isArray()) {
String canonicalName = getComponentType().getCanonicalName();
if (canonicalName != null)
return canonicalName + "[]";
else
return ReflectionData.NULL_SENTINEL;
}
if (isHidden() || isLocalOrAnonymousClass())
return ReflectionData.NULL_SENTINEL;
Class<?> enclosingClass = getEnclosingClass();
if (enclosingClass == null) { // top level class
return getName();
} else {
String enclosingName = enclosingClass.getCanonicalName();
if (enclosingName == null)
return ReflectionData.NULL_SENTINEL;
String simpleName = getSimpleName();
return new StringBuilder(enclosingName.length() + simpleName.length() + 1)
.append(enclosingName)
.append('.')
.append(simpleName)
.toString();
}
}

isArray() 判断该 Class 对象是否为数组。

getComponentType() 返回数组中元素的 Class 对象,如果该对象不是数组,则返回 null

isHidden() 是 JDK15 新增的方法,用于判断一个类是否是隐藏类,像 Lambda 表达式、方法引用就是隐藏类。

isLocalOrAnonymousClass() 判断该 Class 对象是否为本地类(定义在一个代码块中的类,比如定义在方法中、静态代码块中)或匿名类。匿名类和本地类在 Java 中无法呈现出类结构,所在位置不能通过名称表示出来,因此 getCanonicalName() 返回 null

getEnclosingClass() 返回该 Class 对象的封装 Class 对象,如果该 Class 对象是顶级类,则返回 null

getSimpleName()

getSimpleName() 返回的信息可以 不精准地 来标识一个类,因此不能保证唯一性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String getSimpleName() {
ReflectionData<T> rd = reflectionData();
String simpleName = rd.simpleName;
if (simpleName == null) {
rd.simpleName = simpleName = getSimpleName0();
}
return simpleName;
}

// 核心实现
private String getSimpleName0() {
if (isArray()) {
return getComponentType().getSimpleName() + "[]";
}
String simpleName = getSimpleBinaryName();
if (simpleName == null) { // top level class
simpleName = getName();
simpleName = simpleName.substring(simpleName.lastIndexOf('.') + 1); // strip the package name
}
return simpleName;
}

getSimpleBinaryName() 返回该 Class 对象的简单二进制名称。如果该 Class 对象是顶级类,则返回 null;否则以顶级类 getName() 信息的长度为开始截取索引,截取该 Class 对象的 getName() 信息(这是 JDK 8 里的逻辑,JDK 17 中由本地方法实现):

1
2
3
4
5
6
7
8
9
10
11
private String getSimpleBinaryName() {
if (isTopLevelClass())
return null;
String name = getSimpleBinaryName0();
if (name == null) // anonymous class
return "";
return name;
}

// 逻辑由本地方法实现
private native String getSimpleBinaryName0();

getTypeName()

getTypeName() 返回此类型名称的信息字符串,就像 toString() 一样,表示纯粹的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String getTypeName() {
if (isArray()) {
try {
Class<?> cl = this;
int dimensions = 0;
do {
dimensions++;
cl = cl.getComponentType();
} while (cl.isArray());
return cl.getName() + "[]".repeat(dimensions);
} catch (Throwable e) { /*FALLTHRU*/ }
}
return getName();
}

示例对比

目标测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class GetNameTestClass {

public static class Inner {

}

public static void fun() {
class LocalClassInMethod {

}

Class<LocalClassInMethod> localClazz = LocalClassInMethod.class;
System.out.println(localClazz.getName());
System.out.println(localClazz.getCanonicalName());
System.out.println(localClazz.getSimpleName());
System.out.println(localClazz.getTypeName());
}

static {
class LocalClassInStaticBlock {

}

Class<LocalClassInStaticBlock> localClazz = LocalClassInStaticBlock.class;
System.out.println(localClazz.getName());
System.out.println(localClazz.getCanonicalName());
System.out.println(localClazz.getSimpleName());
System.out.println(localClazz.getTypeName());
}
}

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@Test
@SuppressWarnings({"ConstantValue", "InstantiationOfUtilityClass"})
public void testGetName() {
// 基本类型
Class<Integer> intClazz = int.class;
Assertions.assertEquals("int", intClazz.getName());
Assertions.assertEquals("int", intClazz.getCanonicalName());
Assertions.assertEquals("int", intClazz.getSimpleName());
Assertions.assertEquals("int", intClazz.getTypeName());

// 基本类型数组
Class<byte[]> byteClazz = byte[].class;
Assertions.assertEquals("[B", byteClazz.getName());
Assertions.assertEquals("byte[]", byteClazz.getCanonicalName());
Assertions.assertEquals("byte[]", byteClazz.getSimpleName());
Assertions.assertEquals("byte[]", byteClazz.getTypeName());

// 字符串数组
Class<? extends String[]> stringArrayClazz = String[].class;
Assertions.assertEquals("[Ljava.lang.String;", stringArrayClazz.getName());
Assertions.assertEquals("java.lang.String[]", stringArrayClazz.getCanonicalName());
Assertions.assertEquals("String[]", stringArrayClazz.getSimpleName());
Assertions.assertEquals("java.lang.String[]", stringArrayClazz.getTypeName());

// 普通类
Class<GetNameTestClass> clazz = GetNameTestClass.class;
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass", clazz.getName());
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass", clazz.getCanonicalName());
Assertions.assertEquals("GetNameTestClass", clazz.getSimpleName());
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass", clazz.getTypeName());

// 内部类
Class<GetNameTestClass.Inner> innerClazz = GetNameTestClass.Inner.class;
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass$Inner", innerClazz.getName());
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass.Inner", innerClazz.getCanonicalName());
Assertions.assertEquals("Inner", innerClazz.getSimpleName());
Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass$Inner", innerClazz.getTypeName());

// 匿名内部类
Class<? extends Runnable> runnableClazz = new Runnable() {
@Override
public void run() {

}
}.getClass();
Assertions.assertEquals("indi.mofan.reflection.JavaReflectionUtilTest$1", runnableClazz.getName());
Assertions.assertNull(runnableClazz.getCanonicalName());
Assertions.assertEquals("", runnableClazz.getSimpleName());
Assertions.assertEquals("indi.mofan.reflection.JavaReflectionUtilTest$1", runnableClazz.getTypeName());

System.out.println("静态代码块本地类: ");
GetNameTestClass obj = new GetNameTestClass();

System.out.println("静态方法中的本地类: ");
GetNameTestClass.fun();

System.out.println("Lambda 表达式: ");
Supplier<String> supplier = () -> "Function";
var supplierClazz = supplier.getClass();
System.out.println(supplierClazz.getName());
System.out.println(supplierClazz.getCanonicalName());
System.out.println(supplierClazz.getSimpleName());
System.out.println(supplierClazz.getTypeName());

System.out.println("方法引用: ");
Function<Integer, String> fun = String::valueOf;
var funClazz = fun.getClass();
System.out.println(funClazz.getName());
System.out.println(funClazz.getCanonicalName());
System.out.println(funClazz.getSimpleName());
System.out.println(funClazz.getTypeName());
}

运行测试方法后,测试通过,控制台打印出:

静态代码块本地类: 
indi.mofan.pojo.GetNameTestClass$1LocalClassInStaticBlock
null
LocalClassInStaticBlock
indi.mofan.pojo.GetNameTestClass$1LocalClassInStaticBlock
静态方法中的本地类: 
indi.mofan.pojo.GetNameTestClass$1LocalClassInMethod
null
LocalClassInMethod
indi.mofan.pojo.GetNameTestClass$1LocalClassInMethod
Lambda 表达式: 
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
null
JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
方法引用: 
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78
null
JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78

3.7 获取类的泛型信息

getGenericSuperclass()

getGenericSuperclass() 方法用于获取含有泛型信息的父类,如果父类不含泛型信息,该方法等价于 getSuperclass() 方法。

在测试类 GetGenericInfoTest 中有这样两个静态嵌套类:

1
2
3
4
5
static class MyList extends ArrayList<String> {
}

static class MyLinkList<T> extends LinkedList<T> {
}

分别获取 MyListMyLinkList 含有泛型信息的父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void testGetGenericSuperclass() {
Type genericSuperclass = MyList.class.getGenericSuperclass();
Assert.assertEquals("java.util.ArrayList<java.lang.String>",
genericSuperclass.toString());
Assert.assertTrue(genericSuperclass instanceof ParameterizedType);
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Assert.assertArrayEquals(new Type[]{String.class},
parameterizedType.getActualTypeArguments());
Assert.assertEquals(ArrayList.class, parameterizedType.getRawType());
Assert.assertNull(parameterizedType.getOwnerType());
Assert.assertEquals("java.util.ArrayList<java.lang.String>",
parameterizedType.getTypeName());

genericSuperclass = MyLinkList.class.getGenericSuperclass();
Assert.assertEquals("java.util.LinkedList<T>", genericSuperclass.toString());
Assert.assertTrue(genericSuperclass instanceof ParameterizedType);
parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Assert.assertEquals(1, actualTypeArguments.length);
Assert.assertEquals("T", actualTypeArguments[0].toString());
Assert.assertEquals(LinkedList.class, parameterizedType.getRawType());
Assert.assertNull(parameterizedType.getOwnerType());
Assert.assertEquals("java.util.LinkedList<T>", parameterizedType.getTypeName());
}

MyList 来说,其父类是 ArrayList,不含泛型参数,其泛型是确切的 String 类型,因此 ParameterizedType 对象的 getActualTypeArguments() 方法返回的是含有 String.class 的数组。由于 ArrayList 不是某个类的嵌套类,因此 getOwnerType() 方法的返回结果是 null

MyLinkList 来说,其父类是 LinkedList,含有泛型参数 T,因此 ParameterizedType 对象的 getActualTypeArguments() 方法返回的信息中含有泛型参数 TLinkedList 也不是某个类的嵌套类,getOwnerType() 方法的返回结果也是 null

如果要获取实现的接口的泛型信息呢?还可以使用 getGenericSuperclass() 方法吗?

比如在测试类 GetGenericInfoTest 中有以下接口和嵌套类:

1
2
3
4
5
6
7
8
interface MyInterface<T> {
}

static class MyInterfaceImpl_1 implements MyInterface<String> {
}

static class MyInterfaceImpl_2<T> implements MyInterface<T> {
}

尝试使用 getGenericSuperclass() 方法获取 MyInterfaceImpl_1MyInterfaceImpl_2 的实现的接口的泛型信息:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testGetGenericSuperclass_2() {
Type genericSuperclass = MyInterface.class.getGenericSuperclass();
Assert.assertNull(genericSuperclass);

genericSuperclass = MyInterfaceImpl_1.class.getGenericSuperclass();
Assert.assertFalse(genericSuperclass instanceof ParameterizedType);

genericSuperclass = MyInterfaceImpl_2.class.getGenericSuperclass();
Assert.assertFalse(genericSuperclass instanceof ParameterizedType);
}

很遗憾,使用 getGenericSuperclass() 方法并不能成功获取,获取父接口的泛型信息可以使用 getGenericInterfaces() 方法。

getGenericInterfaces()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void testGetGenericInterfaces() {
Type[] genericInterfaces = MyInterface.class.getGenericInterfaces();
Assert.assertEquals(0, genericInterfaces.length);

genericInterfaces = MyInterfaceImpl_1.class.getGenericInterfaces();
Assert.assertEquals(1, genericInterfaces.length);
for (Type genericInterface : genericInterfaces) {
Assert.assertTrue(genericInterface instanceof ParameterizedType);
ParameterizedType type = (ParameterizedType) genericInterface;
Assert.assertArrayEquals(new Type[]{String.class}, type.getActualTypeArguments());
Assert.assertEquals(MyInterface.class, type.getRawType());
Assert.assertEquals(this.getClass(), type.getOwnerType());
}

genericInterfaces = MyInterfaceImpl_2.class.getGenericInterfaces();
Assert.assertEquals(1, genericInterfaces.length);
for (Type genericInterface : genericInterfaces) {
Assert.assertTrue(genericInterface instanceof ParameterizedType);
ParameterizedType type = (ParameterizedType) genericInterface;
Assert.assertEquals("T", type.getActualTypeArguments()[0].toString());
Assert.assertEquals(MyInterface.class, type.getRawType());
Assert.assertEquals(this.getClass(), type.getOwnerType());
}
}

Java 不允许多继承,但允许实现多个接口,因此 getGenericInterfaces() 返回的是一个数组,表示实现的多个接口信息。

在返回的 Type 数组中,如果实现的某个接口带有泛型信息,可以将 Type 对象转换为 ParameterizedType 对象来获取泛型信息。

MyInterface 接口是定义在测试类中的嵌套类,因此调用 getOwnerType() 方法返回的是当前测试类的 Class 对象。

只能使用 getGenericInterfaces() 方法来获取实现的接口的泛型信息,而不能获取继承的父类的泛型信息,否则最终得到的 Type 数组是一个空数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class MyClass<T> {
}

static class SimpleClass extends MyClass<String> {
}

@Test
public void testGetGenericInterfaces_2() {
Type[] genericInterfaces = MyClass.class.getGenericInterfaces();
Assert.assertEquals(0, genericInterfaces.length);

genericInterfaces = SimpleClass.class.getGenericInterfaces();
Assert.assertEquals(0, genericInterfaces.length);
}

需要获取其他位置的泛型信息时,参考:JAVA反射 | 泛型解析

3.8 反射调用可变参数方法

可变参数可以当成对应的数组类型参数。

如果可变参数类型是引用类型:接收到参数后,会自动拆包取出参数再分配给底层方法,因此需要将传入的数组包装成 Object 对象或者将其作为 Object[] 中的一个元素;

如果可变参数类型是基本类型:不会将参数拆包,因此可以不用包装,但包装了也不会抛出异常,为了统一,可以和引用类型的可变参数一样,都包装一层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static class MyClass {
public void firstMethod(int... ints) {
System.out.println("call firstMethod");
}

public void secondMethod(String... strings) {
System.out.println("call secondMethod");
}
}

@Test
@SneakyThrows
@SuppressWarnings("all")
public void testVaryArgsMethod() {
MyClass obj = new MyClass();
Class<MyClass> clazz = MyClass.class;

Method firstMethod = clazz.getDeclaredMethod("firstMethod", int[].class);
firstMethod.invoke(obj, new int[]{1, 2, 3});
firstMethod.invoke(obj, (Object) new int[]{1, 2, 3});
firstMethod.invoke(obj, new Object[]{new int[]{1, 2, 3}});

Method secondMethod = clazz.getDeclaredMethod("secondMethod", String[].class);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> secondMethod.invoke(obj, new String[]{"a", "b", "c"}))
.withMessage("wrong number of arguments");

// 强转为 Object
secondMethod.invoke(obj, (Object) new String[]{"a", "b", "c"});
// 或者是 Object[] 中的一个元素
secondMethod.invoke(obj, new Object[]{new String[]{"a", "b", "c"}});
}