﻿---
title: Reflection In Java
date: 2024-03-25
excerpt: Java Reflection
tags: [Java, Reflection]
thumbnail: https://assets.vluv.space/cover/Dev/Java/Java_Reflection.webp
cover: https://assets.vluv.space/cover/Dev/Java/Java_Reflection.webp
---

<script data-swup-reload-script type="module" src="/js/components/tab.js"></script>

## Reflection

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

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

`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

> [!question] 什么时候会发生类初始化？
>
> - 类的主动引用（一定会发生类的初始化）
>   - 当虚拟机启动，先初始化 main 方法所在的类；
>   - new 一个类的对象；
>   - 调用类的静态成员（除了 final 常量）和静态方法；
>   - 使用 java.lang.reflect 包的方法对类进行反射调用；
>   - 当初始化一个类，如果其父类没有被初始化，则先会初始化它的父类；
> - 类的被动引用（不会发生类的初始化）
>   - 当访问一个静态域时，只有真正声明这个域的类才会被初始化；
>   - 当通过子类引用父类的静态变量，不会导致子类初始化；
>   - 通过数组定义类引用，不会触发此类的初始化；
>   - 引用常量不会触发此类的初始化（常量在链接阶段就存入调用类的常量池中了）

```java
// 获取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
}
```

### Syntax

#### 使用反射获取构造器并创建实例

```java
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Class<?> personClass = Class.forName("Person");
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Object personInstance0 = constructor.newInstance("Alice", 30);
Object personInstance1 = constructor.newInstance(new Object[]{"Alice", 30});
```

### Usage Example

<x-tabs>

<x-tab title="Main.java" active>

```java
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");
    }
}
```

</x-tab>

<x-tab title="Employee.java">

```java
package reflection;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyClassAnnotation {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyMethodAnnotation {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface MyConstructorAnnotation {
    String value();
}

@MyClassAnnotation("This is a class annotation")
public class Employee {
    private int id;
    private double salary;

    public Employee() {
    }

    @MyConstructorAnnotation("This is a constructor annotation")
    public Employee(int eid, double esal) {
        this.id = eid;
        this.salary = esal;
    }

    @MyMethodAnnotation("This is a method annotation")
    public void show() {
        System.out.println("Hello");
    }

    public int getId() {
        return id;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", salary=" + salary + "]";
    }

    class Inner {
        public void print() {
            System.out.println("Inner class");
        }
    }
}
```

</x-tab>

</x-tabs>

```shell
$ 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 工具类，通过字节码生成的方式加快反射速度
- 相对不安全，破坏了封装性（因为通过反射可以获得私有方法和属性）
