一、概述

Java类的加载、连接和初始化知识比较底层,但掌握这些底层的运行原理有助于对Java程序的运行有更好的把握。而Java语言中的反射机制可以操作(读和改)字节码文件,获取某个对象、某个类的运行时信息,并可以动态地创建Java对象,动态地调用Java方法,访问并修改指定对象的成员变量值。Java中反射机制的相关类在java.lang.reflect包下。
反射机制中重要的类:

  • java.lang.Class:代表整个字节码文件,代表一个类型。
  • java.lang.reflect.Method:代表字节码中的方法字节码
  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
  • java.lang.reflect.Field:代表字节码中的属性字节码。

    二、反射机制

    1.获取Class的三种方式

  • 第一种:Class c = Class.forName(”完整的类名带包名”)

  • 第二种:Class c = 对象.getClass()
  • 第三种:Class c = 任何类型.class ```java public class Test{ public static void main(String[] args) throws ClassNotFoundException {

    1. // 方式一
    2. Class c1 = Class.forName("java.lang.String");//c1代表String.class文件,或者String类型
    3. Class c2 = Class.forName("java.lang.Integer");//完整的带包名的类名
    4. Class c3 = Class.forName("java.lang.System");
    5. // 方式二
    6. String s = "abc";
    7. Class x = s.getClass();//x代表String.class字节码文件
    8. System.out.println(c1 == x);//true,表示c1和x指向的内存地址相同
    9. // 方式三,Java语言中任何一种类型,包括基本数据类型,它都有.class属性
    10. Class c4 = String.class;
    11. Class c5 = int.class;
    12. System.out.println(c1 == c4);//true

    } }

  1. <a name="nZLNR"></a>
  2. ## ![image.png](https://cdn.nlark.com/yuque/0/2021/png/22161775/1633843617885-8501defc-d654-49e8-b3cb-33bb2b328ea0.png#clientId=uce692360-fd37-4&from=paste&height=371&id=u348e140c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=371&originWidth=706&originalType=binary&ratio=1&size=48038&status=done&style=none&taskId=u30800696-cb83-4046-9b09-08125a2997a&width=706)
  3. <a name="Pm2fM"></a>
  4. ## 2.通过反射实例化对象
  5. ```java
  6. class User{
  7. }
  8. public class Test{
  9. public static void main(String[] args) throws ClassNotFoundException {
  10. // 通过反射实例化对象
  11. Class c = Class.forName("com.sundegan.User");
  12. try {
  13. //newInstance()调用无参数构造方法完成对象创建
  14. //必须保证无参构造方法存在,否则会有InstantiationException异常
  15. Object obj = c.newInstance();
  16. } catch (InstantiationException e) {
  17. e.printStackTrace();
  18. } catch (IllegalAccessException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

注:newInstance()调用无参数构造方法完成对象创建,必须保证无参构造方法存在,否则会有InstantiationException异常。

3.通过读属性文件实例化对象

  1. //userinfo.properties文件
  2. className=java.util.Date
  1. public class Test{
  2. public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
  3. FileReader reader = new FileReader("userinfo.properties");
  4. Properties pro = new Properties();
  5. pro.load(reader);
  6. reader.close();
  7. String className = pro.getProperty("className");
  8. //通过反射机制实例化对象
  9. Class c = Class.forName(className);
  10. Object obj = c.newInstance();
  11. System.out.println(obj);
  12. }
  13. }
  14. //Sun Oct 10 15:45:57 CST 2021

优点:反射机制可以使程序更加灵活,在不改变Java代码的情况下,只需更改配置文件,就可以做到不同对象的实例化。符合设计模式中的开闭原则,对扩展开放,对修改关闭。

4.Class.forName( )作用

如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(”完整类名”)方法,这个方法会导致类加载,而静态代码块在类加载时执行,且只执行一次。
提示:在JDBC技术中会使用到。

  1. class Myclass{
  2. static {
  3. //静态代码块在类加载时执行,且只执行一次。
  4. System.out.println("静态代码块执行了...");
  5. }
  6. }
  7. public class Test{
  8. public static void main(String[] args) throws ClassNotFoundException {
  9. //Class.forName()方法的执行会导致类加载
  10. Class.forName("com.sundegan.Myclass");
  11. }
  12. }

5.获取类路径下文件的绝对路径

https://www.bilibili.com/video/BV1mE411x7Wt?p=657&share_source=copy_web

6.以流的形式返回

https://www.bilibili.com/video/BV1mE411x7Wt?p=658&share_source=copy_web

7.资源绑定器

https://www.bilibili.com/video/BV1mE411x7Wt?p=659&share_source=copy_web

三、类加载机制

1.类的加载、连接和初始化

1.1 JVM和类

系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。
当运行某个Java程序时,将会启动一个Java虚拟机进程,不管该Java程序多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。同一个JVM下的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当Java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。

  1. class A{
  2. public static int a = 6;
  3. }
  4. public class Test{
  5. public static void main(String[] args) throws ClassNotFoundException {
  6. var a = new A();
  7. a.a++;//让类变量a增加
  8. System.out.println(a.a);//7
  9. }
  10. }
  1. class Test1{
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. var a = new A();
  4. System.out.println(a.a);//6
  5. }
  6. }

Test程序输出7无问题,Test1程序为什么输出6呢?因为运行第二个Test1程序时,程序两次运行JVM进程,第一次运行JVM结束后,它对A类所做的修改将全部丢失,第二次运行JVM时将再次初始化A类。

1.2 类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。即类也是一种对象,系统中所有的类实际上也是实例,它们都是java.lang.Class的实例。
类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常称为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用类加载器,可以从不同的来源加载类的二进制数据,通常有如下几种来源:

  • 从本地文件系统加载class文件。
  • 从JAR包加载class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。

类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

1.3 类的连接

当类被加载之后,系统为之生成一个对应的Class对象,接着进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。

1.4 类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:

  • 声明类变量时指定初始值;
  • 使用静态初始化块为类变量指定初始值。

    1. public class Test{
    2. static
    3. {
    4. b = 6;//使用静态初始化块为变量b指定初始值
    5. }
    6. static int a = 5;
    7. static int b = 9;
    8. static int c;
    9. public static void main(String[] args) {
    10. System.out.println(Test.b);
    11. }
    12. }
    13. //9

    声明变量时指定初始值,静态初始化块都将被当成类的初始化语句,JVM会按这些语句在程序中的排列顺序依次执行它们。、
    JVM初始化一个类包含如下几个步骤:

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类。

  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  • 假如类中有初始化语句,则系统依次执行这些初始化语句。

依次类推,所以JVM最先初始化的总是java.lang.Object类,当程序使用任何一个类时,系统会保证该类及所有父类都会被初始化。

1.5 类初始化的时机

当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口。

  • 创建类的实例。创建实例的方式包括:使用new操作符来创建实例;通过反射来创建实例;通过反序列化的方式来创建实例。
  • 调用某个类的类方法。
  • 访问某个类或接口的类变量,或为该类变量赋值。
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
  • 初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
  • 直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。

除此之外,下面的几种情形需要特别指出。
情形一:对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序中使用该静态类变量,也不会导致该类的初始化。

  1. class MyTest{
  2. static{
  3. System.out.println("静态初始化块...");
  4. }
  5. static final String s = "abc";
  6. }
  7. public class Test{
  8. public static void main(String[] args) {
  9. //访问输出MyTest类中的s类变量
  10. System.out.println(MyTest.s);
  11. }
  12. }
  13. //abc

上面的程序中s类变量在编译时就会被替换成abc,所以该代码不会导致初始化MyTest类。
注:当某个类变量使用了final修饰,而且它的值可以在编译时就确定下来,那么程序其他地方使用该类变量时,实际上并没有使用该类变量,而是相当于使用常量。
情形二:当使用ClassLoader类的loadClass( )方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName( )方法才会导致强制初始化该类。

  1. class MyTest{
  2. static{
  3. System.out.println("静态初始化块...");
  4. }
  5. }
  6. public class Test{
  7. public static void main(String[] args) throws ClassNotFoundException {
  8. ClassLoader c = ClassLoader.getSystemClassLoader();
  9. c.loadClass("com.sundegan.MyTest");//仅仅只是加载该类
  10. System.out.println("系统加载MyTest类...");
  11. Class.forName("com.sundegan.MyTest");//会导致类的初始化
  12. }
  13. }
  14. //系统加载MyTest类...
  15. //静态初始化块...

2.类加载器

JDK中自带了3个类加载器,启动类加载器、扩展类加载器、应用类加载器。代码在执行之前,需要将用到的类全部加载到JVM当中,首先通过启动类加载器加载,专门加载jdk包中jre\lib\rt.jar包下的类文件,rt.jar包中都是JDK最核心的类库。如果通过启动类加载器加载不到的时候,会通过“扩展类加载器”加载,专门加载jre\lib\ext*.jar包下的所有文件。如果扩展类加载器没有加载到,会通过“应用类加载器”加载,专门加载classpath下的jar包。

四、反射应用

1.通过反射查看类信息

前面介绍了获取Class类对象的三种方式,获得该类对象后,即可通过Class类提供的大量方法来获取该Class对象所对应类的详细信息,如获取该类的构造器、方法、属性、注解等。

2.使用反射生成并操作对象

2.1 反射类的属性Field(了解)

  1. class Student{
  2. //4个采用不同访问控制符修饰的Field
  3. public int id;
  4. private String name;
  5. protected int age;
  6. boolean sex;
  7. }
  8. public class Test{
  9. public static void main(String[] args) throws ClassNotFoundException {
  10. //获取类对象
  11. Class studentClass = Class.forName("com.sundegan.Student");
  12. //获取类中所有的public修饰的Field
  13. Field[] field = studentClass.getFields();
  14. System.out.println(field.length);//1
  15. System.out.println(field[0].getName());//id
  16. //获取类中所有的Field
  17. Field[] fields = studentClass.getDeclaredFields();
  18. System.out.println(fields.length);//4
  19. for (Field f:fields) {
  20. //getName获取属性的名字
  21. System.out.println(f.getName());
  22. //getType获取属性的类型
  23. Class fieldType = f.getType();
  24. System.out.println(fieldType.getName());
  25. //获取属性的修饰符
  26. int i = f.getModifiers();//返回的是修饰符代号
  27. //通过Modifier类的静态方法将修饰符代号转换成字符
  28. System.out.println(Modifier.toString(i));
  29. }
  30. }
  31. }

2.2 反射对象的属性(掌握)

2.3 反射对象的方法(掌握)

2.4 反射对象的构造器(掌握)

2.5 反射的对象的父类和父接口