注意:本章节涉及到JVM相关底层原理,难度会有一些大。
反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。
简而言之,我们可以通过反射机制,获取到类的一些属性,包括类里面有哪些字段,有哪些方法,继承自哪个类,甚至还能获取到泛型!它的权限非常高,慎重使用!

Java类加载机制

在学习Java的反射机制之前,我们需要先了解一下类的加载机制,一个类是如何被加载和使用的:
6-1 反射和注解 - 图1
在Java程序启动时,JVM会将一部分类(class文件)先加载(并不是所有的类都会在一开始加载),通过ClassLoader将类加载,在加载过程中,会将类的信息提取出来(存放在元空间中,JDK1.8之前存放在永久代),同时也会生成一个Class对象存放在内存(堆内存),注意此Class对象只会存在一个,与加载的类唯一对应!
思考:既然说和与加载的类唯一对应,那如果我们手动创建一个与JDK包名一样,同时类名也保持一致,那么JVM会加载这个类吗?

  1. package java.lang;
  2. public class String { //JDK提供的String类也是
  3. public static void main(String[] args) {
  4. System.out.println("我姓🐴,我叫🐴nb");
  5. }
  6. }

我们发现,会出现以下报错:

  1. 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
  2. public static void main(String[] args)

但是我们明明在自己写的String类中定义了main方法啊,为什么会找不到此方法呢?实际上这是ClassLoader的双亲委派机制在保护Java程序的正常运行:

image.png
实际上我们的类最开始是由BootstarpClassLoader进行加载,BootstarpClassLoader用于加载JDK提供的类,而我们自己编写的类实际上是AppClassLoader,只有BootstarpClassLoader都没有加载的类,才会让AppClassLoader来加载,因此我们自己编写的同名包同名类不会被加载,而实际要去启动的是真正的String类,也就自然找不到main方法了!

  1. public class Main {
  2. public static void main(String[] args) {
  3. System.out.println(Main.class.getClassLoader()); //查看当前类的类加载器
  4. System.out.println(Main.class.getClassLoader().getParent()); //父加载器
  5. System.out.println(Main.class.getClassLoader().getParent().getParent()); //爷爷加载器
  6. System.out.println(String.class.getClassLoader()); //String类的加载器
  7. }
  8. }

由于BootstarpClassLoader是C++编写的,我们在Java中是获取不到的。