Reflection In Java

Reflection In Java

Reflection

反射(Reflection)允许程序在运行时检查和修改自身的结构和行为。通过反射,程序可以动态地获取类的详细信息,包括类的成员变量、方法、构造器等,并可以在运行时调用这些成员。

这种能力使得Java在某种程度上具有了动态语言的特性。Spring/Spring Boot、MyBatis等框架以及Java注解均大量应用了反射机制。此外,在使用 IDE进行开发时,当输入一个对象或类后按下点号(.),IDE 会自动列出该对象或类的所有可用属性和方法。这一功能背后也是通过反射机制实现的

Dynamic programming language

A dynamic programming language is a type of programming language which allows various operations to be determined and executed at runtime. This is different from the compilation phase. Key decisions about variables, method calls, or data types are made when the program is running, unlike in static languages, where the structure and types are fixed during compilation. Dynamic languages provide flexibility. This allows developers to write more adaptable and concise code.

For instance, in a dynamic language, a variable can start as an integer. It can later be reassigned to hold a string without explicit type declarations. This feature of dynamic typing enables more fluid and less restrictive coding. Developers can focus on the logic and functionality rather than the constraints of the language.

Popular dynamic programming languages include JavaScript, Python, Ruby, PHP, Lua and Perl.The following are generally considered dynamic languages:

  • C# (using Reflection)
  • Java(using Reflection)
  • JavaScript

java.lang.reflect包中提供了reflection API,使用反射,可以在runtime期间进行以下操作:

  • 判断任意一个对象所属的类
  • 构造任意一个类的对象
  • 判断任意一个类所具有的成员变量和方法
  • 获取泛型信息
  • 调用任意一个对象的成员变量和方法
  • 处理注解
  • 生成动态代理

反射相关API

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

Runtime Class

java.lang.Class<T>表示正在运行的Java应用程序中的类和接口

当 Java 程序启动时,类加载器(class loader)负责将 .class 文件中的字节码加载到内存中。
加载过程中,虚拟机会为被使用的class创建一个类型为 java.lang.Class 的实例(Instance),用于表示该类,一般称这个类为runtime class

什么时候会发生类初始化

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化 main 方法所在的类;
    • new 一个类的对象;
    • 调用类的静态成员(除了 final 常量)和静态方法;
    • 使用 java.lang.reflect 包的方法对类进行反射调用;
    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类;
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化;
    • 当通过子类引用父类的静态变量,不会导致子类初始化;
    • 通过数组定义类引用,不会触发此类的初始化;
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取Class实例的4种方法
public void getClazz() throws ClassNotFoundException {
    // 方式一:Employee.class
    Class c1 = Employee.class;
    // 方式二:emploee.getClass()
    Employee p1 = new Employee();
    Class c2 = p1.getClass();
    // 方式三:Class.forName(String classPath);传入类的全路径获取
    Class c3 = Class.forName(""reflection.Employee"");
    // 方式四:使用类的加载器:ClassLoader ( 了解 )
    ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class c4 = classLoader.loadClass(""reflection.Employee"");
    System.out.println(c4); // class reflection.Employee
    System.out.println(c1 == c4); // true
}

Usage Example

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        // Returns the Class object associated with the class or interface with the
        // given string name.
        Class<?> clazz = Class.forName("reflection.Employee");

        printConstructors(clazz);
        printMethods(clazz);
        printClasses(clazz);
        printAnnotations(clazz);
    }

    private static void printConstructors(Class<?> clazz) {
        System.out.println("======== Constructor API ========");

        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("Constructor Name: " + constructor.getName());
            System.out.println("Constructor Parameter Count: " + constructor.getParameterCount());

            Parameter[] parameters = constructor.getParameters();
            for (Parameter parameter : parameters) {
                System.out.println("Constructor's parameter: " + parameter);
            }
        }
        // 通过反射创建对象
        System.out.println("New instance using reflection");
        Constructor<?> cons;
        try {
            cons = clazz.getConstructor(int.class,double.class);
            Employee employee = (Employee) cons.newInstance(1, 2.3);
            System.out.println(employee);
        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }

        System.out.println("======== Constructor API END ========\n");
    }

    private static void printMethods(Class<?> c) {
        System.out.println("======== Method API ========");

        Method[] methods = c.getDeclaredMethods();
        System.out.println("Number of methods: " + methods.length);

        for (Method method : methods) {
            System.out.println("Method name: " + method.getName());
            System.out.println("Method return type: " + method.getReturnType().getName());
            System.out.println("Method parameter count: " + method.getParameterCount());

            Parameter[] parameters = method.getParameters();
            for (Parameter parameter : parameters) {
                System.out.println("Method's Parameter: " + parameter);
            }
        }
        // Invoke method
        System.out.println("Invoke method using reflection");
        try {
            Method method = c.getMethod("getSalary");
            System.out.println("Method return value: " + method.invoke(new Employee()));
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            e.printStackTrace();
        }

        System.out.println("======== Method API END ========\n");
    }

    private static void printClasses(Class<?> c) {
        System.out.println("======== Class API ========");

        Class<?>[] classes = c.getDeclaredClasses();
        for (Class<?> class1 : classes) {
            System.out.println("Class: " + class1.getName());
        }

        System.out.println("======== Class API END ========\n");
    }

    private static void printAnnotations(Class<?> c) {
        System.out.println("======== Annotation API ========");

        // 获取类上的注解
        Annotation[] classAnnotations = c.getDeclaredAnnotations();
        for (Annotation annotation : classAnnotations) {
            System.out.println("Class Annotation: " + annotation);
        }

        // 获取方法上的注解
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] methodAnnotations = method.getDeclaredAnnotations();
            for (Annotation annotation : methodAnnotations) {
                System.out.println("Method Annotation: " + annotation);
            }
        }

        // 获取构造器上的注解
        Constructor<?>[] constructors = c.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            Annotation[] constructorAnnotations = constructor.getDeclaredAnnotations();
            for (Annotation annotation : constructorAnnotations) {
                System.out.println("Constructor Annotation: " + annotation);
            }
        }

        System.out.println("======== Annotation API END ========\n");
    }
}
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
$ java -version
openjdk version "22.0.2" 2024-07-16
OpenJDK Runtime Environment (build 22.0.2+9-70)
OpenJDK 64-Bit Server VM (build 22.0.2+9-70, mixed mode, sharing)
$ java ./Main.java
======== Constructor API ========
Constructor Name: reflection.Employee
Constructor Parameter Count: 0
Constructor Name: reflection.Employee
Constructor Parameter Count: 2
Constructor's parameter: int arg0
Constructor's parameter: double arg1
New instance using reflection
Employee [id=1, salary=2.3]
======== Constructor API END ========

======== Method API ========
Number of methods: 4
Method name: toString
Method return type: java.lang.String
Method parameter count: 0
Method name: getId
Method return type: int
Method parameter count: 0
Method name: getSalary
Method return type: double
Method parameter count: 0
Method name: show
Method return type: void
Method parameter count: 0
Invoke method using reflection
Method return value: 0.0
======== Method API END ========

======== Class API ========
Class: reflection.Employee$Inner
======== Class API END ========

======== Annotation API ========
Class Annotation: @reflection.MyClassAnnotation("This is a class annotation")
Method Annotation: @reflection.MyMethodAnnotation("This is a method annotation")
Constructor Annotation: @reflection.MyConstructorAnnotation("This is a constructor annotation")
======== Annotation API END ========

八股

反射机制的优缺点

优点:

  • 能够运行时动态获取类的实例,提高灵活性;
  • 与动态编译结合

缺点:

  • 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案:
    • 通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
    • 多次创建一个类的实例时,有缓存会快很多
    • ReflectASM工具类,通过字节码生成的方式加快反射速度
  • 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
作者

Jiaxing Gao

发布于

2024-03-25

更新于

2024-10-19

许可协议

评论

}