七: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 使用动态代理来插入横切逻辑(如事务管理、日志记录等)
相关推荐
winks3几秒前
Spring Task的使用
java·后端·spring
云空6 分钟前
《解锁 Python 数据挖掘的奥秘》
开发语言·python·数据挖掘
秋意钟11 分钟前
Spring新版本
java·后端·spring
椰椰椰耶13 分钟前
【文档搜索引擎】缓冲区优化和索引模块小结
java·spring·搜索引擎
mubeibeinv15 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
青莳吖16 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall23 分钟前
期末考学C
java·开发语言
重生之绝世牛码25 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行31 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
Algorithm157641 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang