0%

反射

反射

★★★第二十章 反射

★★★反射机制

  • 反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。

    加载完类之后,在堆中就产生了一个 Class 类型的对象(**Class 也是一个类,类名就叫 Classclass Class。一个类只有一个 Class 对象),这个对象包含了类的完整结构信息,通过这个对象可以得到类的结构。这个 Class 对象就像一面镜子,透过这个镜子可以看到类的结构,所以,形象的称之为:反射**。

  • Java 反射机制可以完成:

    1. 运行时判断任意一个对象所属的类。
    2. 运行时构造任意一个类的对象。
    3. 运行时得到任意一个类所具有的成员变量和方法。
    4. 运行时调用任意一个对象的成员变量和方法。
    5. 生成动态代理。
★★★Java反射机制原理图
  • Java 程序在计算机中有三个阶段:

    1. 代码阶段/编译阶段

      • Java 程序源代码 Cat.java 经过 javac 编译成 .class 字节码文件 Cat.class
    2. **Class 类阶段(加载阶段)**:

      • 当要创建一个 Cat 对象 cat 时,类加载器 ClassLoader 会将 Cat 类的 Class 对象加载到堆中,该对象拥有 Cat 类的结构。在得到 Class 对象后,就可以在堆中创建这个 cat 对象了。
    3. Runtime 运行阶段

      • 被创建的 cat 对象知道自己属于哪一个 Class 对象,然后 cat 对象就可以进行接下来的操作了,例如调用 Cat 类的成员方法:cat.hi()

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C20-1.jpg)

★反射相关类

  1. java.lang.Class:代表一个类,**Class 对象表示某个类加载后在堆中的对象**。
  2. java.lang.reflect.Method:代表类的方法,**Method 对象表示某个类的方法**。
  3. java.lang.reflect.Field:代表类的成员变量,**Field 对象表示某个类的成员变量**。
  4. java.lang.reflect.Constructor:代表类的构造方法,**Constructor 对象表示某个类的构造器**。
  • 主程序:Reflection01.java

    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
    package com.f.chapter20.reflection;

    import java.io.FileInputStream;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Properties;

    /**
    * @author fzy
    * @date 2023/7/24 16:58
    * 反射相关类的使用
    */
    public class Reflection01 {
    public static void main(String[] args) throws Exception {
    //1.使用 Properties 类, 从配置文件中读取类的相关信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("C:\\Users\\1\\Code Project\\Java project\\file\\cat.properties"));
    String classPath = properties.getProperty("class");
    String method = properties.getProperty("method");
    String field = properties.getProperty("field");
    //2.使用Java反射机制
    // 2.1 java.lang.Class的使用
    Class catClass = Class.forName(classPath); //根据配置文件得到Cat类的Class对象
    Object o = catClass.newInstance(); //根据该Class对象创建Cat类的对象
    // 2.2 java.lang.reflect.Method的使用
    Method catMethod = catClass.getMethod(method); //根据配置文件得到Cat类的相应方法
    //传统方法: 对象.方法(); 反射机制: 方法.invoke(对象)
    catMethod.invoke(o); //通过invoke函数调用该对象的该相应方法
    // 2.3 java.lang.reflect.Field的使用
    //gatField不能得到类的私有成员变量
    Field nameField = catClass.getField(field); //根据配置文件得到Cat类的相应字段
    //传统方法: 对象.成员变量; 反射机制: 成员变量.get(对象)
    System.out.println(nameField.get(o)); //通过get函数得到该对象的该相应成员变量
    // 2.4 java.lang.reflect.Constructor的使用
    Constructor constructor1 = catClass.getConstructor(); //()中可以指定构造器的参数类型
    System.out.println(constructor1); //public com.f.chapter20.Cat()
    Constructor constructor2 = catClass.getConstructor(String.class); //这里传入的是String类的Class对象
    System.out.println(constructor2); //public com.f.chapter20.Cat(java.lang.String)
    }
    }
  • 配置文件:cat.properties

    1
    2
    3
    class=com.f.chapter20.Cat
    method=showAge
    field=age
  • 相关的类:Cat.java

    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
    package com.f.chapter20;

    /**
    * @author fzy
    * @date 2023/7/24 17:01
    */
    public class Cat {
    private String name = "Milk";
    public int age = 10;

    public Cat() {
    }

    public Cat(String name) {
    this.name = name;
    }

    public void hi() {
    System.out.println(name + " say hi!");
    }

    public void showAge() {
    System.out.println(name + " is " + age + " years old.");
    }
    }

反射调用优化

  • 反射优点和缺点:

    1. 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
    2. 缺点:使用反射基本是解释执行,对执行速度有影响。
  • 反射调用优化 - 关闭访问检查

    1. MethodFieldConstructor 对象都有 setAccessible() 方法。
    2. setAccessible 作用是启动和禁用访问安全检查的开关。
    3. 参数值为 true 表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false 则表示对反射的对象执行访问检查。

★★★Class类

  • public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement
    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C20-2.jpg)

    1. Class 也是类,因此也继承 Object 类。
    2. Class 类对象不是 new 出来的,而是系统创建的
    3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
    4. 每个类的实例都会记得自己是由哪个 Class 实例所生成的。
    5. 通过 Class 类对象可以完整地得到一个类的完整结构,通过一系列 API 调用即可。
    6. Class 对象是存放在堆的
    7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)。
★Class类的常用方法
  • 最根本的是要先得到对应类的 Class 类对象
  1. Field[] getFields():获取所有 public 修饰的属性,包括本类及其父类的
  2. Field[] getDeclaredFields():获取该类中所有的属性。
  3. Method[] getMethods:获取所有 public 修饰的方法,包括本类及其父类的
  4. Method[] getDeclaredMethods:获取该类中所有的方法。
  5. Constructor<?>[] getConstructors():获取所有 public 修饰的构造器,只包括本类的
  6. Constructor<?>[] getDeclaredConstructors():获取该类中所有的构造器。
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
package com.f.chapter20.class_;

import com.f.chapter20.Car;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* @author fzy
* @date 2023/7/24 18:13
* Class类的常用方法
*/
public class Class01 {
public static void main(String[] args) throws Exception {
String classPath = "com.f.chapter20.Car";
//1.`static Class forName(String name)`:返回指定类名 `name` 的 `Class` 对象。
//<?>表示不确定的Java类型
Class<?> carClass = Class.forName(classPath);
// 1.1 显示是哪个类的Class对象
System.out.println(carClass); //class com.f.chapter20.Car
// 1.2 得到Class对象的运行类型 -> 即为 Class 类
System.out.println(carClass.getClass()); //class java.lang.Class
//2.getPackage():得到Class对象所对应的类的包名
System.out.println(carClass.getPackage()); //package com.f.chapter20
//3.`getName()`:返回此 `Class` 对象所表示的实体(类、接口、数组类、基本类型等)名称。
System.out.println(carClass.getName()); //com.f.chapter20.Car
//4.`Object newlnstance()`:调用缺省构造函数,返回该 `Class` 对象的一个实例。
Car car = (Car) carClass.newInstance();
System.out.println(car); //Car{brand='宝马', price=500000.0, color='red', speed=50.0}
//5.getField:返回一个Field对象,它表示该类的相应字段(该字段需要为public)。
Field brand = carClass.getField("brand");
System.out.println(brand.get(car)); //宝马
// 5.1 通过反射给属性赋值
brand.set(car, "宾利");
System.out.println(car); //Car{brand='宾利', price=500000.0, color='red', speed=50.0}
//6.getFields:获取所有 public 修饰的属性,包括本类及其父类的
Field[] fields = carClass.getFields();
for (Field field : fields) {
System.out.print(field.getName() + " "); //brand
}
System.out.println();
//7.`Field[] getDeclaredFields()`:获取该类中所有的属性
Field[] declaredFields = carClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.print(declaredField.getName() + " "); //brand price color speed
}
System.out.println();
//8.getMethods:获取所有 public 修饰的方法,包括本类及其父类的
Method[] methods = carClass.getMethods();
for (Method method : methods) {
//除了 m1 和 toString 以外,其他是 Car 父类 Object 的 public 方法
System.out.print(method.getName() + " "); //toString m1 wait wait wait equals hashCode getClass notify notifyAll
}
System.out.println();
//9.getDeclaredMethods:获取该类中所有的方法
Method[] declaredMethods = carClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.print(declaredMethod.getName() + " "); //m3 m2 m4 m1 toString
}
System.out.println();
//10.`Constructor[] getConstructors()`:获取所有 `public` 修饰的构造器,只包括本类的。
Constructor<?>[] constructors = carClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.print(constructor + " "); //public com.f.chapter20.Car()
}
System.out.println();
//11.getDeclaredConstructors:获取该类中所有的构造器
Constructor<?>[] declaredConstructors = carClass.getDeclaredConstructors();
//private com.f.chapter20.Car(java.lang.String,double,java.lang.String)
//com.f.chapter20.Car(java.lang.String,double)
//protected com.f.chapter20.Car(java.lang.String)
//public com.f.chapter20.Car()
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//12.`Class getSuperclass()`:返回该类的直接父类的 `Class` 对象。
Class<?> superclass = carClass.getSuperclass();
System.out.println(superclass); //class java.lang.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.f.chapter20;

/**
* @author fzy
* @date 2023/7/24 19:50
*/
public class Car {
public String brand = "宝马";
protected double price = 500000;
String color = "red";
private double speed = 50;

public Car() {
}

protected Car(String brand) {
}

Car(String brand, double price) {
}

private Car(String brand, double price, String color) {
}

public void m1() {

}

protected void m2() {

}

void m3() {

}

private void m4() {

}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
", speed=" + speed +
'}';
}
}
★★★获取Class对象的六种方法
  • 对应于 Java 程序在计算机中的三个阶段,有不同的获取 Class 对象的方法:

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C20-3.jpg)

  1. 前提:已知一个类的全类名,且该类在类路径下,则可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException。例如:Class carClass1 = Class.forName(classPath);

    应用场景:多用于配置文件,读取类的全路径,加载类

  2. 前提:若已知具体的类,通过类的 class 获取,该方式最为安全可靠,程序性能最高。例如:Class carClass2 = Car.class;

    应用场景:多用于参数传递,比如通过反射得到对应构造器对象。例如下面的代码:Constructor constructor2 = catClass.getConstructor(String.class); 中的 String.class

  3. 前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。例如:Class carClass3 = car.getClass();
    应用场景:通过创建好的对象,获取 Class 对象

  4. 通过类加载器 ClassLoader 来获取到类的 Class 对象。例如:Class carClass4 = classLoader.loadClass(classPath);

  5. 基本数据类型按如下方式得到 Class 类对象:Class cls1 = 基本数据类型.class;

  6. 基本数据类型对应的包装类,可以通过 .TYPE 得到对应的 Class 类对象:Class cls2 = 包装类.TYPE;

    • 注意:基本数据类型及其对应的包装类,它们的 Class 类对象是同一个。
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
package com.f.chapter20.class_;

import com.f.chapter20.Car;

/**
* @author fzy
* @date 2023/7/24 21:01
* 得到Class对象的各种方式
*/
public class GetClass_ {
public static void main(String[] args) throws Exception {
//1.已知一个类的全类名,且该类在类路径下,则可通过 `Class` 类的静态方法 `forName()` 获取,
// 可能抛出 `ClassNotFoundException`。
String classPath = "com.f.chapter20.Car"; //classPath可通过配置文件读取
Class carClass1 = Class.forName(classPath);
System.out.println(carClass1); //class com.f.chapter20.Car

//2.若已知具体的类,通过类的 `class` 获取,该方式最为安全可靠,程序性能最高。
Class carClass2 = Car.class;
System.out.println(carClass2); //class com.f.chapter20.Car

//3.已知某个类的实例,调用该实例的 `getClass()` 方法获取 `Class` 对象。
Car car = new Car();
Class carClass3 = car.getClass();
System.out.println(carClass3); //class com.f.chapter20.Car

//4.通过类加载器 `ClassLoader` 来获取到类的 `Class` 对象。
ClassLoader classLoader = car.getClass().getClassLoader();
Class carClass4 = classLoader.loadClass(classPath);
System.out.println(carClass4); //class com.f.chapter20.Car

//上面的四个类对象实际就是同一个对象
System.out.println(carClass1.equals(carClass2) && carClass2.equals(carClass3) && carClass3.equals(carClass4)); //true

//5.基本数据类型按如下方式得到 Class 类对象:`Class cls1 = 基本数据类型.class;`。
Class<Integer> cls1 = int.class;
System.out.println(cls1); //int
System.out.println(cls1.hashCode()); //1554874502

//6.基本数据类型对应的包装类,可以通过 `.TYPE` 得到对应的 Class 类对象:`Class cls2 = 包装类.TYPE;`
Class<Integer> cls2 = Integer.TYPE;
System.out.println(cls2); //int
System.out.println(cls2.hashCode()); //1554874502
}
}

拥有Class类对象的类型

  • 如下类型有 Class 类对象:
    1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类。
    2. interface:接口。
    3. 数组。
    4. enum:枚举。
    5. annotation:注解。
    6. 基本数据类型。
    7. void

★★★类加载

静态加载和动态加载
  1. 静态加载编译时加载相关的类,如果没有该类则报错,依赖性太强。
  2. 动态加载运行时加载需要的类,如果运行时不使用该类,则即使该类不存在,也不报错,降低了依赖性。
★类加载时机
  1. 当创建对象时 (new)。-> 静态加载
  2. 当子类被加载时,父类也加载。 -> 静态加载
  3. 调用类中的静态成员时。 -> 静态加载
  4. 通过反射。-> 动态加载
★★★类加载流程图
  • 类加载对应于 “★★★Java反射机制原理图” 小节的第二个阶段。

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C20-4.jpg)

  • 类加载三个阶段:
    1. **加载 Loading**:将类的 .class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成。
    2. **连接 Linking**:将类的二进制数据合并到 JRE 中。
      • 验证 verification:对文件的安全进行校验,如文件描述符、元数据、字节码文件安全性等。
      • 准备 preparation对静态变量进行默认初始化并分配内存空间。
      • 解析 resolution:把符号引用转换为直接引用。
    3. **初始化 initialization**:JVM 负责对类进行初始化,这里主要是指静态成员。
加载阶段
  • JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存(方法区)中,并生成一个代表该类的 java.lang.Class 对象
连接阶段 - 验证
  1. 目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  2. 包括:文件格式验证(是否以魔数 oxcafebabe 开头)、元数据验证、字节码验证和符号引用验证。
  3. 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段 - 准备
  • JVM 会在该阶段对静态变量,分配内存并初始化(对应数据类型的默认初始值如 00Lnullfalse 等)。这些变量所使用的内存都将在方法区中进行分配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.f.chapter20.classload;

    /**
    * @author fzy
    * @date 2023/7/25 15:17
    * 类在加载阶段中,连接阶段-准备 的分析
    */
    public class ClassLoad_ {
    }

    class A {
    //n1 是实例属性,不是静态属性,因此在准备阶段是不会分配内存的
    public int n1 = 10;
    //n2 是静态变量,在"连接阶段-准备"时,在方法区中分配内存,n2 默认初始化为 0,在初始化阶段才会赋值为 20
    public static int n2 = 20;
    //N3 是静态常量,因为它一旦赋值就不变了,所以在"连接阶段-准备"时,N3 就直接赋值为 30
    public static final int N3 = 30;
    }
连接阶段 - 解析
  • 虚拟机将常量池内的符号引用替换为直接引用的过程。

    Class 类对象还没有创建(分配内存)时,通过符号来引用;在 Class 类对象创建(分配内存)后,通过内存地址来引用,即直接引用。

初始化阶段
  1. 到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 <clinit>() 方法的过程。
  2. <clinit>() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
  3. 虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕

★获取类结构信息

  • 在 “★Class类的常用方法” 小节中,含有一系列获取类结构信息的方法,如获取类的 FieldMethodConstructor 对象。接下来的内容是在获取了类的 FieldMethodConstructor 对象后,进一步获得这些对象的相关信息。下面代码例子中的 Car 类,就是在 “★Class类的常用方法” 小节中的 Car 类。
java.lang.reflect.Field
  1. getModifiers:以 int 形式返回修饰符(默认修饰符为0public1private2protected4static8final16)。

    如果是 public static 则返回 1+8=9,以此类推。

  2. getType:返回属性所对应的类的 Class 对象。

  3. 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
31
32
33
34
package com.f.chapter20.class_;

import java.lang.reflect.Field;

/**
* @author fzy
* @date 2023/7/25 16:29
*/
public class GetClassFieldInfo_ {
public static void main(String[] args) throws Exception {
//先得到对应类的 `Class` 类对象
String classPath = "com.f.chapter20.Car";
Class<?> carClass = Class.forName(classPath);
//1.getModifiers:以 int 形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。
// 如果是 public static 则返回 1+8=9...
//brand 属性的修饰符的值 = 1
//price 属性的修饰符的值 = 4
//color 属性的修饰符的值 = 0
//speed 属性的修饰符的值 = 2
Field[] declaredFields = carClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName() + " 属性的修饰符的值 = " + declaredField.getModifiers());
}

//2.getType:返回属性所对应的类的 Class 对象
//brand 属性的类型 = class java.lang.String
//price 属性的类型 = double
//color 属性的类型 = class java.lang.String
//speed 属性的类型 = double
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName() + " 属性的类型 = " + declaredField.getType());
}
}
}
java.lang.reflect.Method
  1. getModifiers:以 int 形式返回修饰符(默认修饰符为0public1private2protected4static8final16)。

    如果是 public static 则返回 1+8=9,以此类推。

  2. getReturnType:返回方法所对应的类的 Class 对象。

  3. getName:返回方法名。

  4. getParameterTypes:以 Class[] 形式返回方法的形参的类型的 Class 对象数组。

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
package com.f.chapter20.class_;

import java.lang.reflect.Method;

/**
* @author fzy
* @date 2023/7/25 16:51
*/
public class GetClassMethodInfo_ {
public static void main(String[] args) throws Exception {
//先得到对应类的 `Class` 类对象
String classPath = "com.f.chapter20.Car";
Class<?> carClass = Class.forName(classPath);
//1.getModifiers:以 int 形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。
//toString 方法的修饰符的值 = 1
//m3 方法的修饰符的值 = 0
//m2 方法的修饰符的值 = 4
//m1 方法的修饰符的值 = 1
//m4 方法的修饰符的值 = 2
Method[] declaredMethods = carClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + " 方法的修饰符的值 = " + declaredMethod.getModifiers());
}

//2.getReturnType:返回方法所对应的类的 Class 对象
//toString 方法的类型 = class java.lang.String
//m1 方法的类型 = void
//m2 方法的类型 = void
//m3 方法的类型 = void
//m4 方法的类型 = void
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + " 方法的类型 = " + declaredMethod.getReturnType());
}

//3.getParameterTypes:以 Class[] 形式返回方法的形参的类型的数组
//toString 方法的形参类型 =
//m3 方法的形参类型 =
//m1 方法的形参类型 =
//m4 方法的形参类型 =
//m2 方法的形参类型 =
//因为这几个方法的参数都为空,所以parameterTypes都是空数组
for (Method declaredMethod : declaredMethods) {
System.out.print(declaredMethod.getName() + " 方法的形参类型 = ");
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType + " ");
}
System.out.println();
}
}
}
java.lang.reflect.Constructor
  1. getModifiers:以 int 形式返回修饰符(默认修饰符为0public1private2protected4static8final16)。

    如果是 public static 则返回 1+8=9,以此类推。

  2. getName:返回构造器名(全类名)。

  3. getParameterTypes:以 Class[] 形式返回构造器的形参的类型的 Class 对象数组。

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
package com.f.chapter20.class_;

import java.lang.reflect.Constructor;

/**
* @author fzy
* @date 2023/7/25 17:06
*/
public class GetClassConstructorInfo_ {
public static void main(String[] args) throws Exception {
//先得到对应类的 `Class` 类对象
String classPath = "com.f.chapter20.Car";
Class<?> carClass = Class.forName(classPath);
//1.getModifiers:以 int 形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。
//private com.f.chapter20.Car(java.lang.String,double,java.lang.String) 构造器的修饰符的值 = 2
//com.f.chapter20.Car(java.lang.String,double) 构造器的修饰符的值 = 0
//protected com.f.chapter20.Car(java.lang.String) 构造器的修饰符的值 = 4
//public com.f.chapter20.Car() 构造器的修饰符的值 = 1
Constructor<?>[] declaredConstructors = carClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor + " 构造器的修饰符的值 = " + declaredConstructor.getModifiers());
}

//2.getParameterTypes:以 Class[] 形式返回构造器的形参的类型的数组
//private com.f.chapter20.Car(java.lang.String,double,java.lang.String) 构造器的形参类型 = class java.lang.String double class java.lang.String
//com.f.chapter20.Car(java.lang.String,double) 构造器的形参类型 = class java.lang.String double
//protected com.f.chapter20.Car(java.lang.String) 构造器的形参类型 = class java.lang.String
//public com.f.chapter20.Car() 构造器的形参类型 =
for (Constructor declaredConstructor : declaredConstructors) {
System.out.print(declaredConstructor + " 构造器的形参类型 = ");
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType + " ");
}
System.out.println();
}
}
}

★通过反射创建对象

  1. 方式一:调用类中的 public 修饰的无参构造器。
  2. 方式二:调用类中的指定构造器。
  3. Class 类相关方法:
    • newInstance:调用类中的无参构造器,获取对应类的对象。
    • getConstructor(Class.clazz):根据参数列表,获取对应的构造器对象(需要为public)
    • getDecalaredConstructor(Class.clazz):根据参数列表,获取对应的构造器对象
  4. Constructor 类相关方法:
    • setAccessible:暴破
    • newlnstance(Object..obj):调用构造器
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
package com.f.chapter20.reflection;

import java.lang.reflect.Constructor;

/**
* @author fzy
* @date 2023/7/25 17:58
* 通过反射创建对象
*/
public class ReflectionCreateInstance {
public static void main(String[] args) throws Exception {
//1.先获取到User类的Class对象
Class<?> userClass = Class.forName("com.f.chapter20.reflection.User");
//2.通过public的无参构造器创建实例
Object o1 = userClass.newInstance();
System.out.println(o1); //User{age=10, name='小明'}
//3.通过public的有参构造器创建实例
/**
* 这里的Constructor对象就是
* public User(String name) { //public的有参构造器
* this.name = name;
* }
*/
// 3.1 先得到对应的构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
// 3.2 再创建实例,并传入实参
Object o2 = constructor.newInstance("小强");
System.out.println(o2); //User{age=10, name='小强'}
//4.通过非public的有参构造器创建实例
/**
* 这里的Constructor对象就是
* private User(int age, String name) { //private的有参构造器
* this.age = age;
* this.name = name;
* }
*/
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
//由于这里的 declaredConstructor 是 private 的,所以需要暴破(暴力破解)
declaredConstructor.setAccessible(true); //暴破,使用反射可以访问 private 构造器/方法/属性
Object o3 = declaredConstructor.newInstance(100, "张三丰");
System.out.println(o3); //User{age=100, name='张三丰'}
}
}

class User {
private int age = 10;
private String name = "小明";

public User() { //public的无参构造器
}

public User(String name) { //public的有参构造器
this.name = name;
}


private User(int age, String name) { //private的有参构造器
this.age = age;
this.name = name;
}

@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
暴破
  • 通过上面的例子可以看到,使用反射可以访问类的私有的构造器/方法/属性,相当于暴力破解(暴破名称的由来)了类的私有、外部不可访问机制。

★通过反射访问类中的属性

  1. 根据属性名获取 Field 对象:Field f = clazz对象.getDeclaredField(属性名);
  2. 暴破:f.setAccessible(true); //f是Field
  3. 访问:
    • f.set(o, 值);
    • System.out.println(f.get(o));
  4. 注意:如果是静态属性,则 setget 中的参数 o,可以写成 null
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
package com.f.chapter20.reflection;

import java.lang.reflect.Field;

/**
* @author fzy
* @date 2023/7/25 19:37
* 通过反射访问类中的属性
*/
public class ReflectionAccessProperty {
public static void main(String[] args) throws Exception {
//1.先获取到Student类的Class对象
Class<?> studentClass = Class.forName("com.f.chapter20.reflection.Student");
//2.创建对象
Object o = studentClass.newInstance();
System.out.println(o); //Student{age=0, name='null'}
//3.使用反射得到 age 属性对象
Field ageField = studentClass.getField("age");
ageField.set(o, 88); //通过反射来操作属性
System.out.println(ageField.get(o)); //通过反射来得到属性的值 //88
System.out.println(o); //Student{age=88, name='null'}
//4.使用反射操作 name 属性对象 -> (private + static)
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true); //通过暴破来使得可以访问 private 属性
nameField.set(o, "小明");
System.out.println(Student.getName()); //小明
System.out.println(nameField.get(o)); //小明
System.out.println(o); //Student{age=88, name='小明'}
nameField.set(null, "李华"); //因为name是static属性,所以参数可以传入 null
System.out.println(nameField.get(null)); //获取属性值时能传入null,是因为 name 是 static 的 //李华
System.out.println(Student.getName()); //李华
System.out.println(o); //Student{age=88, name='李华'}
}
}

class Student {
public int age;
private static String name;

public Student() {
}

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

public static String getName() {
return name;
}
}

★通过反射访问类中的方法

  1. 根据方法名参数列表获取 Method 方法对象:Method m = clazz.getDeclaredMethod(方法名,XX.class);
  2. 获取对象:Object o = clazz.newlnstance();
  3. 暴破:m.setAccessible(true);
  4. 访问:Object returnValue = m.invoke(o, 实参列表);
  5. 注意:如果是静态方法,则 invoke 的参数 o,可以写成 null
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
package com.f.chapter20.reflection;

import java.lang.reflect.Method;

/**
* @author fzy
* @date 2023/7/25 20:19
* 通过反射访问类中的方法
*/
public class ReflectionAccessMethod {
public static void main(String[] args) throws Exception {
//1.先获取到Boss类的Class对象
Class<?> bossClass = Class.forName("com.f.chapter20.reflection.Boss");
//2.创建对象
Object o = bossClass.newInstance();
//3.调用public的hi方法
// 3.1 得到 hi 方法对象
Method hiMethod = bossClass.getMethod("hi", String.class);
// 3.2 通过 invoke 调用 hi 方法
hiMethod.invoke(o, "Jack"); //hi, Jack
//4.调用private static的say方法
Method sayMethod = bossClass.getDeclaredMethod("say", int.class, String.class, char.class);
sayMethod.setAccessible(true); //因为say方法是私有的,所以需要暴破
System.out.println(sayMethod.invoke(o, 100, "小明", 'A')); //100 小明 A
//因为say方法是Boss类的静态方法,所以传入null也可以
System.out.println(sayMethod.invoke(null, 200, "李华", 'B')); //200 李华 B
//5.在反射中,如果方法有返回值,统一返回 Object,但是返回值的运行类型和方法定义的返回类型一致
Object returnVal = sayMethod.invoke(null, 300, "张三", 'C');
System.out.println(returnVal.getClass()); //class java.lang.String
}
}

class Boss {
public int age;
private static String name;

private static String say(int n, String s, char c) {
return n + " " + s + " " + c;
}

public void hi(String s) {
System.out.println("hi, " + s);
}
}

★★★反射相关方法小结

  1. 在 “★Class类的常用方法” 小节中的方法,主要是用于获得类的 FieldMethodConstructor 对象。
  2. 在 “★获取类结构信息” 小节中的方法,主要是用于对得到的类的 FieldMethodConstructor 对象,可以进一步得到这些对象的相关信息。
  3. 在 “★通过反射创建对象”、“★通过反射访问类中的属性”、“★通过反射访问类中的方法” 小节中的方法,主要是用于根据得到的类的 FieldMethodConstructor 对象,能对类的实例进行操作。
---------------The End---------------