七:java 基础知识(7)-- java 反射(基本操作了解反射是什么)

目录

[1. Java 反射概述](#1. Java 反射概述)

[1.1 反射的定义](#1.1 反射的定义)

[1.2 反射的用途](#1.2 反射的用途)

[1.3 反射与类加载器的关系](#1.3 反射与类加载器的关系)

[2. 反射的基本操作](#2. 反射的基本操作)

[2.1 获取类的信息:Class 类](#2.1 获取类的信息:Class 类)

[2.2 创建对象:Constructor 类](#2.2 创建对象:Constructor 类)

[2.3 获取成员变量:Field 类](#2.3 获取成员变量:Field 类)

[2.4 获取方法:Method 类](#2.4 获取方法:Method 类)

[2.5 获取构造方法:Constructor 类](#2.5 获取构造方法:Constructor 类)

[3. 反射与性能问题](#3. 反射与性能问题)

[4. Java 反射应用场景](#4. Java 反射应用场景)

[3.1 反射的性能开销](#3.1 反射的性能开销)

[3.2 如何优化反射的使用](#3.2 如何优化反射的使用)

[4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配)](#4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配))

[4.2 动态代理](#4.2 动态代理)


1. Java 反射概述

复制代码
##### 1.1 反射的定义

* 反射是指在程序运行时,能够动态地获取类的元数据,并且可以操作该类的对象、构造器、字段、方法等。反射主要通过 Java 的 `Class` 类和相关的 `Field`、`Method`、`Constructor` 类实现。

* 反射的基本操作包括:

  * **获取类的信息** :通过 `Class` 类。

  * **实例化对象** :通过 `Constructor` 类。

  * **访问字段** :通过 `Field` 类。

  * **调用方法** :通过 `Method` 类。

  * **获取类的构造方法** :通过 `Constructor` 类。
复制代码
##### 1.2 反射的用途

* **动态加载类**:

  * 在程序运行时动态加载类,不需要在编译时确定类。

  * 通过 `Class.forName()` 方法可以加载指定的类,例如在 JDBC 中动态加载数据库驱动。

* **动态创建对象**:

  * 通过反射可以在程序运行时创建类的实例,而无需显式的 `new` 操作。

* **访问和修改对象属性**:

  * 通过反射,可以访问和修改对象的私有字段(包括私有字段、方法、构造器等),这是常规方法不能做到的。

* **调用方法**:

  * 可以通过反射动态调用对象的任何方法,无论是公共方法还是私有方法。

* **框架设计**:

  * 许多框架(如 Spring、Hibernate)都依赖于反射来实现功能,如动态生成对象、自动注入依赖、处理注解等。

* **开发工具和库**:

  * 反射常用于开发通用的工具类库、代码生成工具、插件框架等。

* **实现动态代理**:

  * 通过 `Proxy` 类和反射,可以实现动态代理机制,这在 AOP(面向切面编程)等场景中非常常见。
复制代码
##### 1.3 反射与类加载器的关系

* **类加载器的基本概念**:

  * 类加载器是 Java 虚拟机(JVM)的一部分,负责在程序运行时加载类的字节码文件(`.class` 文件)。它可以根据需要加载、卸载类,并管理类的生命周期。

  * 类加载器有三种主要类型:

    * **Bootstrap ClassLoader**:加载 JDK 核心类库。

    * **Extension ClassLoader**:加载 JDK 扩展目录下的类库。

    * **Application ClassLoader**:加载应用程序类路径下的类。

* **反射与类加载器的结合**:

  * 反射通常通过 `Class.forName()` 方法加载类。这个方法不仅是反射的一部分,还是由类加载器来加载指定类的字节码的。

  * 反射操作需要类的 `Class` 对象,而 `Class` 对象的获取方式包括通过类加载器加载类的字节码。在 Java 中,类加载器和反射配合使用,使得类可以在运行时动态地被加载并操作。

* **动态加载与反射**:

  * 通过反射,开发者可以在运行时动态决定要加载的类。例如,JDBC 连接池的实现通常会使用反射来加载数据库驱动类,这样程序就能够在没有提前知道驱动类的情况下运行。

* **不同类加载器的反射行为**:

  * 反射的类加载通常由类加载器控制,不同的类加载器可能会加载相同的类(例如,用户定义的类和 JDK 类)。这意味着在使用反射时,类加载器可能会影响类的访问权限,尤其是在存在不同类加载器隔离的环境下(如应用服务器中不同模块之间的类加载隔离)。

2. 反射的基本操作

复制代码
##### 2.1 获取类的信息:`Class` 类

* **基本概念**
  * 在 Java 中,反射的第一步通常是获取类的元数据(即类本身的信息)。这通常通过 `Class` 类来完成。`Class` 类是 Java 反射机制的核心,它代表了类的结构信息,包括类名、字段、方法、构造器等。通过反射,可以动态地获取类的定义,并操作类的成员。

* **`Class` 类**
  * `Class` 类是每个 Java 类的元类(metaclass),它包含了该类的所有信息,如类名、字段、方法、构造方法、父类、实现的接口等。每个类都有一个唯一的 `Class` 对象,它是在类加载时由 Java 虚拟机自动创建的。

  * **获取 `Class` 对象的方式**:

    * `Class.forName(String className)`:通过类的全限定名(即类路径)获取 `Class` 对象。

    * `.getClass()`:通过对象实例获取 `Class` 对象。

    * `.class`:通过类名直接获取 `Class` 对象。

* **`Class.forName()`**
  * `Class.forName(String className)` 是一个静态方法,它根据给定的类名(全路径名)动态加载类,并返回该类的 `Class` 对象。这个方法常用于反射中,尤其是当你在编写通用代码或框架时,不知道具体类的情况下,动态加载和反射类信息。

  * **返回类型** :返回 `Class<?>` 类型的对象,表示加载的类的元数据。

  * **异常** :`ClassNotFoundException`,如果指定的类无法找到,会抛出该异常。

  *

        try {
            // 通过类的全路径名称加载类
            Class<?> clazz = Class.forName("java.util.ArrayList");
            System.out.println("Class Name: " + clazz.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

* **`getClass()`** 方法
  * `getClass()` 是 `Object` 类的方法(因为所有类都继承自 `Object`),用于获取当前对象的 `Class` 对象。当你有一个类的实例时,可以调用该方法来获取该实例的元数据。

  * **返回类型** :返回当前对象的 `Class` 对象。

  * **注意** :`getClass()` 是一个实例方法,只能通过对象实例调用

  *

        String str = "Hello, World!";
        Class<?> clazz = str.getClass();
        System.out.println("Class Name: " + clazz.getName());

* **`getName()`、`getSimpleName()`** 等方法
  * **通过 `Class` 对象,可以调用以下方法来获取类的相关信息:**

  * **`getName()` 方法返回类的全限定名,即包括包名的类名。**

  * **`getCanonicalName()` 返回类的规范名称,对于嵌套类,它返回外部类的名称加上嵌套类的名称。**

  * **`getPackage()` 方法返回类所在包的 `Package` 对象,可以通过 `Package` 对象获取包的名称。**

  * **getModifiers():返回类的访问修饰符(如 `public`、`private`、`protected`)。**

  * **getSuperclass():返回该类的父类的 `Class` 对象。**

  * **getInterfaces():返回该类实现的接口数组。**

  * **getDeclaredFields():返回类声明的所有字段(包括私有字段)。**

  * **getDeclaredMethods():返回类声明的所有方法(包括私有方法)。**

  * **getDeclaredConstructors():返回类声明的所有构造方法(包括私有构造方法)。**

  *

            public static void main(String[] args) {
                try {
                    // 1. 使用 Class.forName() 获取 Class 对象
                    Class<?> clazz = Class.forName("java.lang.String");

                    // 2. 使用 getName() 获取全类名
                    System.out.println("Full Class Name: " + clazz.getName());

                    // 3. 使用 getSimpleName() 获取类名
                    System.out.println("Simple Class Name: " + clazz.getSimpleName());

                    // 4. 获取父类
                    Class<?> superClass = clazz.getSuperclass();
                    System.out.println("Superclass: " + superClass.getName());

                    // 5. 获取实现的接口
                    Class<?>[] interfaces = clazz.getInterfaces();
                    System.out.println("Interfaces:");
                    for (Class<?> iface : interfaces) {
                        System.out.println(iface.getName());
                    }

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

    ![](https://i-blog.csdnimg.cn/direct/e25d912564ff456f84ad24ffb766531f.png)
复制代码
##### 2.2 创建对象:`Constructor` 类

* 通过构造器实例化对象

  * 要通过反射创建对象,首先需要获取类的 `Constructor` 对象,然后调用 `newInstance()` 方法来实例化对象。

  *

    ```java
    package com.lirui;

    import java.lang.reflect.Constructor;

    public class DemoService {


        public static void main(String[] args) throws Exception {
            // 获取类的 Class 对象
            Class<?> clazz = MyClass.class;

            // 获取带参数构造器
            Constructor<?> constructor = clazz.getConstructor(String.class, int.class);

            // 通过构造器创建对象
            MyClass obj = (MyClass) constructor.newInstance("张三", 25);
            obj.sayHello();
        }
    }

    class MyClass {
        private final String name;
        private final int age;

        // 带参数的构造器
        public MyClass(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println(name + age);
        }

        public void sayHello() {
            System.out.println("Hello" + name + age);
        }
    }
    ```

* 私有构造器访问
  *

    ```java
    package com.lirui;

    import java.lang.reflect.Constructor;

    public class DemoService {


        public static void main(String[] args) throws Exception {
            // 获取类的 Class 对象
            Class<?> clazz = MyClass.class;

            // 获取带参数构造器
            Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);

            // 通过构造器创建对象
            MyClass obj = (MyClass) constructor.newInstance("张三", 25);
            obj.sayHello();
        }
    }

    class MyClass {
        private final String name;
        private final int age;

        // 私有带参数的构造器
        private  MyClass(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println(name + age);
        }

        public void sayHello() {
            System.out.println("Hello" + name + age);
        }
    }
    ```
复制代码
##### 2.3 获取成员变量:`Field` 类

* `Field` 类
  * `Field` 类是 Java 反射机制中提供的一个类,表示类的字段。你可以使用它来获取字段信息、修改字段值、设置字段可访问性等

* 通过反射获取字段

  * 通过反射获取字段,可以使用 `Class` 类的 `getDeclaredField()` 或 `getField()` 方法。区别在于:

    * `getField()` 只能获取公有字段(`public`)。

    * `getDeclaredField()` 可以获取包括私有字段(`private`)在内的所有字段。

* 获取/修改字段值
  * `Field` 类提供了两个重要方法来获取字段和修改字段值:

    * `get(Object obj)`:获取字段的值。

    * `set(Object obj, Object value)`:修改字段的值。

  *

    ```java
    package com.lirui;

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

    public class DemoService {


        public static void main(String[] args) throws Exception {
            // 创建对象
            MyClass obj = new MyClass("张三", 30);

            // 获取 Class 对象
            Class<?> clazz = obj.getClass();

            // 获取 public 字段
            Field field = clazz.getField("name");

            // 获取字段的值
            String nameValue = (String) field.get(obj);
            System.out.println(" name: " + nameValue);

            // 修改字段的值
            field.set(obj, "李四");

            // 再次获取字段的值
            nameValue = (String) field.get(obj);
            System.out.println("name: " + nameValue);
        }
    }

    class MyClass {
        public String name;
        private int age;

        public MyClass(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    ```
复制代码
##### 2.4 获取方法:`Method` 类

* `Method` 类
  * 通过反射获取方法,可以使用 `Class` 类的 `getMethod()` 或 `getDeclaredMethod()` 方法。它们的区别是:

    * `getMethod()` 只能获取公共方法(`public`)。

    * `getDeclaredMethod()` 可以获取类的所有方法,包括私有方法。

* 通过反射调用方法 /私有的换换就行了
  *

    ```java
    package com.lirui;

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

    public class DemoService {


        public static void main(String[] args) throws Exception {
            // 创建对象
            MyClass obj = new MyClass();

            // 获取 Class 对象
            Class<?> clazz = obj.getClass();

            // 获取方法 sum(int, int)
            Method method = clazz.getMethod("sum", int.class, int.class);

            // 调用方法并传递参数
            Object result = method.invoke(obj, 5, 3); // 输出:Sum is: 8
            System.out.println("Result: " + result);
        }
    }

    class MyClass {
        public int sum(int a, int b) {
            int result = a + b;
            System.out.println("Sum is: " + result);
            return result;
        }
    }
    ```
复制代码
##### 2.5 获取构造方法:`Constructor` 类

* 获取类的构造方法
  * 你可以使用 `Class` 对象的 `getConstructor` 或 `getDeclaredConstructor` 方法来获取类的构造方法。`getConstructor` 获取公共构造方法,而 `getDeclaredConstructor` 获取所有构造方法(包括私有构造方法)。

* 使用反射实例化对象
  *

    ```java
        public static void main(String[] args) {
            try {
                // 获取 Class 对象
                Class<?> clazz = Class.forName("com.example.MyClass");

                // 获取无参构造方法
                Constructor<?> noArgConstructor = clazz.getConstructor();
                System.out.println("无参构造方法: " + noArgConstructor);

                // 获取带参构造方法
                Constructor<?> paramConstructor = clazz.getConstructor(String.class, int.class);
                System.out.println("带参构造方法: " + paramConstructor);

                // 使用反射实例化对象
                Object instance1 = noArgConstructor.newInstance();
                System.out.println("实例化的对象: " + instance1);

                Object instance2 = paramConstructor.newInstance("Hello", 42);
                System.out.println("实例化的对象: " + instance2);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    ```

3. 反射与性能问题

4. Java 反射应用场景

复制代码
##### 3.1 反射的性能开销

* **方法调用延迟:** 通过反射调用方法时,JVM 无法进行正常的内联优化。相比于直接调用,反射需要额外的步骤,如查找方法、参数类型检查、权限验证等。

* **类型检查:** 反射调用方法时,JVM 会执行额外的类型检查和转换,尤其是对于泛型类型或参数类型的匹配,这些都会增加性能开销。

* **反射缓存:** 每次使用反射获取类信息(如方法、字段等)时,都会进行一定的查找过程。频繁的查找和实例化对象可能导致性能问题。

* **不可预测性:** 反射无法享受编译时类型检查和优化。JVM 不能对反射操作进行 JIT(即时编译)优化,因此会影响执行效率。
复制代码
##### 3.2 如何优化反射的使用

* **缓存反射结果,避免重复查找。**
  *

    ```java
      // 缓存方法信息
        private static final Map<String, Method> methodCache = new HashMap<>();

        public static Object invokeMethod(Object obj, String methodName, Object[] params) throws Exception {
            Method method = methodCache.get(methodName);
            if (method == null) {
                // 如果缓存中没有,获取并缓存方法
                method = obj.getClass().getMethod(methodName, (Class<?>[]) params);
                methodCache.put(methodName, method);
            }
            return method.invoke(obj, params);
        }
    ```

* **尽量减少反射操作的次数,避免频繁调用。**
* **在性能敏感部分避免使用反射。**
* **通过动态代理代替反射操作。**
复制代码
##### 4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配)

* 反射用于自动加载配置文件,并自动实例化和装配 Bean。例如,Spring 中的 Bean 自动装配就是通过反射机制来根据配置文件(如 XML 或注解)自动创建对象并注入依赖。

* Spring 会扫描项目中所有的类,然后根据配置的注解(如 `@Component`, `@Autowired`)或 XML 配置来自动创建和注入对象。反射在此过程中的应用如下:

  * **扫描类路径** :Spring 会通过反射扫描项目中的类,查找带有 `@Component`, `@Service`, `@Repository` 等注解的类。

  * **创建 Bean 实例**:通过反射创建这些标注的类的实例,并调用它们的构造方法。

  * **自动装配依赖** :使用反射根据 `@Autowired` 注解,自动装配字段和方法。
复制代码
##### 4.2 动态代理

* Spring AOP 使用动态代理来插入横切逻辑(如事务管理、日志记录等)
相关推荐
渣哥41 分钟前
原来 Java 里线程安全集合有这么多种
java
间彧1 小时前
Spring Boot集成Spring Security完整指南
java
间彧1 小时前
Spring Secutiy基本原理及工作流程
java
Java水解2 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆4 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学5 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole5 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊5 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端
程序员鱼皮5 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码