【java进阶】------反射与动态代理

【部分代码涉及io流与集合的知识点,后续会补】

1. 反射

1.1 反射的概念

  • 反射 :指程序在运行状态中,可以对任意一个类的结构信息 进行动态获取 ,并可以对对象的属性和方法 进行动态调用

反射的关键价值不在于"绕过正常调用方式",而在于把原本编译期确定的类型访问,转化为运行期可决策、可配置、可扩展的元数据访问。换言之,反射使程序能够在不知道某个类的具体编译期类型时,仍然通过类名、字段名、方法名等信息完成对象创建与行为调用。

核心 :反射强调的是运行期结构解析能力 。它把 ClassConstructorFieldMethod 等元对象暴露给程序,使程序可以像处理普通对象一样处理类的结构信息。


1.2 核心前置:Java文件、字节码文件与字节码对象

在正式学习反射的 API 之前,必须厘清三个递进的核心概念:

  • .java 源文件:程序员日常编写的普通 Java 代码文件。
  • .class 字节码文件.java 文件经过编译器编译后生成的二进制文件(物理存在于硬盘上,通过文件管理器肉眼可见)。
  • Class 字节码文件对象 :当 JVM(Java虚拟机)运行并需要使用某个类时,会将对应的 .class 文件加载到内存中,并自动为其创建一个对象。这个对象内部包含了该类的所有核心结构:构造方法、成员变量、成员方法。

重点结论

  • 反射获取的到底是什么? 反射操作的根本目标,就是这个存在于内存中的字节码文件对象
  • 唯一性 :由于同一个类的 .class 字节码文件在一次程序运行过程中只会被 JVM 加载一次,因此这个字节码文件对象在内存中是绝对唯一的

1.3 学习反射到底学什么

理解了字节码文件对象后,反射就不再是抽象的概念了。它其实就是围绕这个唯一的 Class 字节码对象展开的一组结构化操作。

  • 获取 Class 字节码对象(一切反射的入口)。
  • 获取构造方法,并通过构造方法创建对象。
  • 获取成员变量,并完成赋值或取值。
  • 获取成员方法,并完成动态调用。

1.4 获取 Class 对象的三种方式

1.4.1 核心 API

获取方式 典型写法 适用场景
Class.forName("全类名") Class.forName("reflection.one.Student") 最常用于配置文件驱动
类名.class Student.class 适合已知类型的静态引用
对象.getClass() s.getClass() 适合已存在对象时获取真实运行时类型

注意 :正如 1.2 节所强调的,同一个 .class 字节码文件在一次程序运行中只会被加载一次,因此这三种方式最终获得的都是内存中同一个绝对唯一的 Class 对象


1.4.2 代码示例



1.4.2.1 Student
java 复制代码
package reflection.one;

/**
 * 学生实体类,用于演示反射操作。
 * 包含基本属性、构造方法、Getter/Setter 以及 toString 方法。
 */
public class Student {
    private String name;
    private int age;

    /**
     * 无参构造方法。
     * 规范:在使用反射创建对象时(如 Class.newInstance()),通常依赖无参构造方法,因此必须显式保留。
     */
    public Student() {
    }

    /**
     * 全参构造方法。
     *
     * @param name 学生姓名
     * @param age  学生年龄
     */
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取学生姓名。
     *
     * @return name 当前学生的姓名
     */
    public String getName() {
        return name;
    }

    /**
     * 设置学生姓名。
     *
     * @param name 要设置的姓名
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取学生年龄。
     *
     * @return age 当前学生的年龄
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置学生年龄。
     *
     * @param age 要设置的年龄
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 重写 toString 方法,方便在控制台打印对象的具体属性值,而非内存地址。
     *
     * @return 包含学生姓名和年龄的格式化字符串
     */
    @Override
    public String toString() {
        return "Student{name = '" + name + "', age = " + age + "}";
    }
}

1.4.2.2 MyReflectDemo1
java 复制代码
package reflection.one;

/**
 * 反射演示类:展示获取 Class 字节码对象的三种常见方式。
 */
public class MyReflectDemo1 {
    
    public static void main(String[] args) throws ClassNotFoundException {
        
        /*
         * 获取 Class 对象的三种方式核心总结:
         * 1. Class.forName("全类名"); 
         * 2. 类名.class;
         * 3. 对象.getClass();
         *
         * 核心原理:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载到内存中一次。
         * 因此,无论通过哪种方式获取的 Class 对象,指向的都是内存中的同一个实例。
         */

        /*
         * 【细节拓展】为什么要写 Class<?> 而不是直接写 Class?
         * 1. 自 JDK 1.5 起,Class 被设计为泛型类 Class<T>。
         * 2. <?> 是 Java 泛型中的"无界通配符",代表"某种未知的具体类型"。
         * 3. 规范要求:如果只写 Class(原始类型),IDE 会报黄色警告(Raw Use)。
         * 加上 <?> 可以消除警告,明确告诉编译器:"我知道这里有泛型,但我目前不需要限定具体的类型",从而提升代码的严谨性。
         */

        // 1. 第一种方式:Class.forName("全类名")
        // 全类名 = 包名 + 类名
        // 应用场景:最为常用。通常配合配置文件使用,将类名提取到配置文件中,通过读取字符串来动态加载类,从而实现解耦。
        Class<?> clazz1 = Class.forName("reflection.one.Student");

        // 2. 第二种方式:类名.class
        // 应用场景:一般更多的是当作参数进行传递。例如在锁对象 synchronized(Student.class) 或传递类型参数时使用。
        Class<?> clazz2 = Student.class;

        // 3. 第三种方式:对象.getClass()
        // 应用场景:当我们已经创建了该类的对象实例时才可以使用。通常用于多态场景下,判断传入的对象的真实运行时类型。
        Student s = new Student();
        Class<?> clazz3 = s.getClass();

        // 验证结果:比较三个 Class 对象的内存地址是否相同
        System.out.println("clazz1 == clazz2 : " + (clazz1 == clazz2)); // 预期输出: true
        System.out.println("clazz2 == clazz3 : " + (clazz2 == clazz3)); // 预期输出: true
    }
}

总结Class 对象是反射体系的入口。没有 Class,后续的构造器、字段、方法都无法被定位。


1.5 获取构造方法

构造方法反射的目标,是让程序在运行期根据结构信息创建对象。尤其当目标类型来自配置文件、插件扫描或框架容器时,程序无法在源码中直接 new 出固定对象,此时就必须通过 Constructor 完成实例化。

1.5.1 核心 API

  • Class 类中用于【获取】构造方法
方法声明 功能核心说明
Constructor<?>[] getConstructors() 返回本类中所有被 public 修饰的构造方法(数组)
Constructor<?>[] getDeclaredConstructors() 返回本类中所有的 构造方法(数组),包含 privateprotected 及默认权限
Constructor<T> getConstructor(Class<?>... parameterTypes) 根据参数类型,精准返回指定的 public 构造方法(单个)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 根据参数类型,精准返回指定的任意权限构造方法(单个)
  • Constructor 类中用于【操作与内省】构造方法
方法声明 功能核心说明
void setAccessible(boolean flag) 【权限控制】 传入 true 时,强行取消 Java 的访问权限检查(即"暴力反射"必备)
T newInstance(Object... initargs) 【对象实例化】 传入实际参数,执行该构造方法来创建并返回对象实例
int getModifiers() 【内省元数据】 获取该构造方法的修饰符 (返回底层整数,需配合 Modifier 解析)
Parameter[] getParameters() 【内省元数据】 获取该构造方法的所有参数对象(返回数组,可借此进一步获取参数名和类型)
String getName() 【内省元数据】 获取该构造方法的名称(注:构造方法名称固定与全类名一致)

1.5.2 代码示例



1.5.2.2 Student
java 复制代码
package reflection.two;

/**
 * 学生实体类,用于演示更深入的反射操作。
 * 特意设计了不同访问权限 的构造方法。
 */
public class Student {
    
    // ===== 成员变量 =====
    private String name;
    private int age;

    // ===== 构造方法 =====
    
    /**
     * public 无参构造
     */
    public Student() {
    }

    /**
     * public 单参构造
     */
    public Student(String name) {
        this.name = name;
    }

    /**
     * protected 单参构造
     */
    protected Student(int age) {
        this.age = age;
    }

    /**
     * private 双参构造(外部无法直接 new,必须依靠反射 + 暴力破解)
     */
    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    // ===== Getters / Setters / toString =====

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

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

1.5.2.1 ConstructorReflectDemo
java 复制代码
package reflection.two;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

/**
 * 反射演示类:利用反射获取并操作【构造方法】 (Constructor)
 * * =====================【核心 API 总结】=====================
 * * 1. Class 类中用于获取构造方法的方法:
 * - Constructor<?>[] getConstructors()                               : 获取当前类中所有的 public 构造方法。
 * - Constructor<?>[] getDeclaredConstructors()                       : 获取当前类中所有的构造方法(含 public、protected、默认、private)。
 * - Constructor<T> getConstructor(Class<?>... parameterTypes)        : 获取当前类中指定的 public 构造方法。
 * - Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取当前类中指定的任意权限的构造方法。
 * * 2. Constructor 类中用于【操作与内省】构造方法的方法:
 * - void setAccessible(boolean flag)                                 : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
 * - T newInstance(Object... initargs)                                : 根据指定的参数,调用此构造方法创建对象实例。
 * - int getModifiers()                                               : 【内省】获取该构造方法的修饰符(返回底层整数,需配合 Modifier 解析)。
 * - Parameter[] getParameters()                                      : 【内省】获取该构造方法的所有参数对象(Parameter 数组)。
 * * =========================================================
 */
public class ConstructorReflectDemo {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // 1. 获取 Class 字节码文件对象
        Class<?> clazz = Class.forName("reflection.two.Student");

        // ================= 获取多个构造方法 =================
        // 2.1 获取目标类中所有被 public 关键字修饰的构造方法
        System.out.println("--- 1. 测试 getConstructors() ---");
        Constructor<?>[] cons1 = clazz.getConstructors();
        for (Constructor<?> con : cons1) {
            System.out.println(con); // 预期:只能打印出 public 的构造方法
        }

        // 2.1 获取目标类中所有构造方法
        System.out.println("\n--- 2. 测试 getDeclaredConstructors() ---");
        Constructor<?>[] cons2 = clazz.getDeclaredConstructors();
        for (Constructor<?> con : cons2) {
            System.out.println(con); // 预期:能打印出所有的构造方法 (包含 private 等)
        }

        // ================= 获取指定的单个构造方法 =================

        System.out.println("\n--- 3. 测试获取指定的单个构造方法 ---");

        // 3.1 获取无参构造
        Constructor<?> con1 = clazz.getDeclaredConstructor();
        System.out.println("获取无参: " + con1);

        // 3.2 获取带一个 String 参数的构造
        Constructor<?> con2 = clazz.getDeclaredConstructor(String.class);
        System.out.println("获取单参(String): " + con2);

        // 3.3 获取带一个 int 参数的构造
        Constructor<?> con3 = clazz.getDeclaredConstructor(int.class);
        System.out.println("获取单参(int): " + con3);


        // ================= 核心操作:内省与暴力反射 =================

        System.out.println("\n--- 4. 深入操作 private 双参构造 ---");
        // 目标:获取 private Student(String name, int age)
        // 注意:因为目标构造方法是 private 的,所以必须调用带 Declared 的方法
        Constructor<?> con4 = clazz.getDeclaredConstructor(String.class, int.class);

        // 4.1 获取权限修饰符
        int modifiers = con4.getModifiers();
        System.out.println("权限修饰符: " + Modifier.toString(modifiers) + " (对应整数: " + modifiers + ")");

        // 4.2 获取该构造方法的参数列表
        Parameter[] parameters = con4.getParameters();
        System.out.println("参数列表:");
        for (Parameter parameter : parameters) {
            System.out.println(" - " + parameter);
        }

        /*
         * 【核心细节:暴力反射】
         * 由于 con4 是 private 修饰的私有构造,正常情况下外部类无权调用。
         * 调用 setAccessible(true) 表示"忽略访问控制权限",强行访问。
         * 警告:如果不加这一句,直接调用 newInstance 会抛出 IllegalAccessException(非法访问异常)。
         */
        con4.setAccessible(true);

        // 4.3 通过反射执行该构造方法,传入实际参数,创建实例对象
        Student stu = (Student) con4.newInstance("张三", 23);
        System.out.println("\n暴力反射创建对象成功: " + stu);
    }
}

总结getDeclaredConstructor 负责"拿到",setAccessible(true) 负责"允许访问",newInstance 负责"真正创建"。


1.6 获取成员变量

成员变量反射用于读取和修改对象内部状态 。它常用于通用数据导出、对象映射、序列化框架、依赖注入等场景。

1.6.1 核心 API

  • Class 类中用于【获取】成员变量
方法声明 功能核心说明
Field[] getFields() 返回所有被 public 修饰的成员变量(数组)
Field[] getDeclaredFields() 返回所有的 成员变量(数组),包含 privateprotected 及默认权限
Field getField(String name) 根据变量名,返回指定的 public 成员变量(单个)
Field getDeclaredField(String name) 根据变量名,返回指定的任意权限成员变量(单个)
  • Field 类中用于【操作】成员变量
方法声明 功能核心说明
void setAccessible(boolean flag) 【权限控制】 传入 true 时,强行取消 Java 的访问权限检查("暴力反射"必调)
void set(Object obj, Object value) 【数据操作】给指定对象 obj 的该成员变量赋值value
Object get(Object obj) 【数据操作】 获取 指定对象 obj 中该成员变量记录的值
Class<?> getType() 【内省元数据】获取该成员变量的数据类型 (如 String.classint.class
String getName() 【内省元数据】获取该成员变量的名称

1.6.2 代码示例



1.6.2.1 Student
java 复制代码
package reflection.three;

/**
 * 标准的 JavaBean 实体类 (gender 特意使用了 public)
 */
public class Student {
    
    // ================= 1. 成员变量私有化 =================
    private String name;
    private int age;
    public String gender;

    // ================= 2. 构造方法 =================
    
    /**
     * 无参构造
     */
    public Student() {
    }

    /**
     * 全参构造
     */
    public Student(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    // ================= 3. 公共的 Getter 和 Setter 方法 =================

    /**
     * 获取姓名
     * @return name 当前学生的姓名
     */
    public String getName() { 
        return name; 
    }

    /**
     * 设置姓名
     * @param name 要设置的姓名
     */
    public void setName(String name) { 
        this.name = name; 
    }

    /**
     * 获取年龄
     * @return age 当前学生的年龄
     */
    public int getAge() { 
        return age; 
    }

    /**
     * 设置年龄
     * @param age 要设置的年龄
     */
    public void setAge(int age) { 
        this.age = age; 
    }

    /**
     * 获取性别
     * @return gender 当前学生的性别
     */
    public String getGender() { 
        return gender; 
    }

    /**
     * 设置性别
     * @param gender 要设置的性别
     */
    public void setGender(String gender) { 
        this.gender = gender; 
    }

    // ================= 其他方法 =================

    /**
     * 重写 toString 方法,方便在控制台直观地打印对象的属性值
     */
    @Override
    public String toString() {
        return "Student{name = '" + name + "', age = " + age + ", gender = '" + gender + "'}";
    }
}

1.6.2.2 FieldReflectDemo
java 复制代码
package reflection.three;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * 反射演示类:利用反射获取并操作【成员变量】 (Field)
 * * =====================【核心 API 总结】=====================
 * * 1. Class 类中用于获取成员变量的方法:
 * - Field[] getFields()                              : 返回所有 public 成员变量对象的数组(包括父类的)。
 * - Field[] getDeclaredFields()                      : 返回所有成员变量对象的数组(仅限本类,包含 private、protected 等,不含父类)。
 * - Field getField(String name)                      : 返回指定的 public 成员变量对象。
 * - Field getDeclaredField(String name)              : 返回指定的任意权限的成员变量对象。
 * * 2. Field 类中用于【操作与内省】变量的方法:
 * - void setAccessible(boolean flag)                 : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
 * - void set(Object obj, Object value)               : 将指定对象 (obj) 中该成员变量的值设置为 value。
 * - Object get(Object obj)                           : 获取指定对象 (obj) 中该成员变量的值。
 * - Class<?> getType()                               : 【内省】获取该成员变量的数据类型(如 String.class)。
 * - String getName()                                 : 【内省】获取该成员变量的名称。
 * - int getModifiers()                               : 【内省】获取该成员变量的修饰符(返回底层整数,需配合 Modifier 解析)。
 * * =========================================================
 */
public class FieldReflectDemo {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        // 1. 获取 Class 字节码文件对象
        Class<?> clazz = Class.forName("reflection.three.Student");

        // ================= 获取所有成员变量 =================
        System.out.println("--- 获取所有的成员变量 ---");
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }


        // ================= 获取单个成员变量并内省 =================
        System.out.println("--- 1. 获取指定的单个成员变量 ---");
        // 目标:获取 private String name;
        Field nameField = clazz.getDeclaredField("name");
        System.out.println("获取到的 Field 对象: " + nameField);

        // 1.1 获取权限修饰符
        int modifiers = nameField.getModifiers();
        System.out.println("权限修饰符: " + Modifier.toString(modifiers) + " (对应整数: " + modifiers + ")");

        // 1.2 获取成员变量的名字
        String n = nameField.getName();
        System.out.println("成员变量的名字: " + n);

        // 1.3 获取成员变量的数据类型
        Class<?> type = nameField.getType();
        System.out.println("成员变量的数据类型: " + type);

        // ================= 核心操作:读取与修改对象中的值 =================
        System.out.println("\n--- 2. 操作对象中记录的值 ---");
        
        // 准备一个实际的对象实例,用于提取和修改数据
        Student s = new Student("zhangsan", 23, "男");
        System.out.println("原始对象: " + s);

        /*
         * 【核心细节:暴力反射】
         * 因为 name 字段是 private 的,外部类无法直接读写。
         * 必须通过 setAccessible(true) 取消权限校验。
         */
        nameField.setAccessible(true);

        // 2.1 获取值:调用 get 方法,传入要提取数据的对象实例
        String value = (String) nameField.get(s);
        System.out.println("通过反射提取到的 name 值: " + value);

        // 2.2 修改值:调用 set 方法,参数一:要修改的对象实例;参数二:要赋的新值
        nameField.set(s, "lisi");
        System.out.println("通过反射修改后的对象: " + s);
    }
}

注意:字段反射可以突破封装边界,因此在工程实践中应控制使用范围。反射不是替代面向对象封装的常规手段,而是框架层、工具层处理通用对象结构的底层机制。


1.7 获取成员方法

成员方法反射的本质,是把普通方法调用转换为运行期的元数据调用。它允许程序先定位方法对象,再通过 invoke 进行执行。

1.7.1 核心 API

  • Class 类中用于【获取】成员方法
方法声明 功能核心说明
Method[] getMethods() 返回所有被 public 修饰 的成员方法(数组),包含父类继承的公共方法
Method[] getDeclaredMethods() 返回本类中所有的 成员方法(数组),包含私有方法,但不包含继承的方法
Method getMethod(String name, Class<?>... parameterTypes) 根据方法名和参数类型,精准返回指定的 public 成员方法(单个)
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 根据方法名和参数类型,精准返回指定的任意权限成员方法(单个)
  • Method 类中用于【运行与内省】成员方法
方法声明 功能核心说明
void setAccessible(boolean flag) 【权限控制】 传入 true 时,强行取消 Java 的访问权限检查("暴力反射"必备)
Object invoke(Object obj, Object... args) 【动态运行】 执行指定方法。 • 参数一 (obj) :用哪个对象去调用该方法 • 参数二 (args) :调用方法传递的实际参数(没有就不写) • 返回值:方法执行后的返回值(没有就不写或返回 null)
int getModifiers() 【内省元数据】获取该方法的修饰符 (返回底层整数,需配合 Modifier 解析)
String getName() 【内省元数据】 获取该方法的名称
Parameter[] getParameters() 【内省元数据】 获取该方法的所有参数对象 (返回 Parameter 数组)
Class<?>[] getExceptionTypes() 【内省元数据】 获取该方法声明抛出的所有异常类型 (返回 Class 数组)

面试常考避坑指南

getMethods() 会顺藤摸瓜把父类(比如 Object 类中的 waitequalshashCode 等)所有的 public 方法都抓取出来。而 getDeclaredMethods() 则非常专一,只抓取当前类自己写的代码,哪怕是 private 也抓,但绝对不抓父类的。


1.7.2 代码示例

1.7.2.1 MyReflectDemo
java 复制代码
package reflection.four;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

/**
 * 反射演示类:利用反射获取并操作【成员方法】 (Method)
 * * =====================【核心 API 总结】=====================
 * * 1. Class 类中用于获取成员方法的方法:
 * - Method[] getMethods()                                         : 返回所有公共成员方法对象的数组,包括继承的。
 * - Method[] getDeclaredMethods()                                 : 返回所有成员方法对象的数组,不包括继承的。
 * - Method getMethod(String name, Class<?>... parameterTypes)     : 返回指定的公共成员方法对象。
 * - Method getDeclaredMethod(String name, Class<?>... parameterTypes): 返回指定的任意权限成员方法对象。
 * * 2. Method 类中用于【运行与内省】方法的方法:
 * - void setAccessible(boolean flag)                              : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
 * - Object invoke(Object obj, Object... args)                     : 运行方法。
 * > 参数一 (obj): 用 obj 对象调用该方法。
 * > 参数二 (args): 调用方法的传递的实际参数(如果没有就不写)。
 * > 返回值: 方法的返回值(如果没有就不写,或者返回 null)。
 * - int getModifiers()                                            : 【内省】获取该方法的修饰符(返回底层整数,需配合 Modifier 解析)。
 * - String getName()                                              : 【内省】获取该方法的名字。
 * - Parameter[] getParameters()                                   : 【内省】获取该方法的所有参数对象(Parameter 数组)。
 * - Class<?>[] getExceptionTypes()                                : 【内省】获取该方法声明抛出的所有异常类型数组。
 * * =========================================================
 */
public class MyReflectDemo {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        // 1. 获取 Class 字节码文件对象
        Class<?> clazz = Class.forName("reflection.four.Student");

        // ================= 获取所有的成员方法 (包含父类公共方法) =================
        System.out.println("获取所有的成员方法 (包含父类公共方法): ");
        Method[] methods1 = clazz.getMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }


        // ================= 获取本类所有的成员方法 (不含父类,含私有) =================
        System.out.println("获取本类所有的成员方法 (不含父类,含私有): ");
        Method[] methods2 = clazz.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }

        // ================= 获取指定的单一方法并进行内省 =================
        System.out.println("--- 1. 获取指定的单个方法 ---");
        // 目标:获取 private String eat(String something) throws IOException, NullPointerException
        Method m = clazz.getDeclaredMethod("eat", String.class);
        System.out.println("获取到的 Method 对象: " + m);

        // 1.1 获取方法的修饰符
        int modifiers = m.getModifiers();
        System.out.println("权限修饰符: " + Modifier.toString(modifiers));

        // 1.2 获取方法的名字
        String name = m.getName();
        System.out.println("方法的名字: " + name);

        // 1.3 获取方法的形参
        System.out.println("方法的形参列表:");
        Parameter[] parameters = m.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(" - " + parameter);
        }

        // 1.4 获取方法的抛出的异常
        System.out.println("方法抛出的异常类型:");
        Class<?>[] exceptionTypes = m.getExceptionTypes();
        for (Class<?> exceptionType : exceptionTypes) {
            System.out.println(" - " + exceptionType);
        }

        // ================= 核心操作:方法运行 (invoke) =================
        System.out.println("\n--- 2. 方法运行 ---");
        
        Student s = new Student();

        // 因为是 private 方法,必须进行暴力反射
        m.setAccessible(true);

        // 参数一 s: 表示方法的调用者
        // 参数二 "汉堡包": 表示在调用方法的时候传递的实际参数
        // 返回值强转:因为我们已知返回值是 String 类型,所以可以直接强转接收
        String result = (String) m.invoke(s, "汉堡包");
        
        System.out.println("方法的返回值: " + result);
    }
}

1.7.2.2 Student
java 复制代码
package reflection.four;

import java.io.IOException;

/**
 * 学生实体类,专门用于演示更深入的反射操作(主要测试成员方法 Method 的反射)。
 */
public class Student {

    // ================= 成员变量 =================
    private String name;
    private int age;

    // ================= 构造方法 =================
    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // ================= 成员方法 (专门用于反射测试) =================

    public void sleep() {
        System.out.println("睡觉");
    }

    /**
     * 【重点修改】
     * 1. 权限为 private
     * 2. 返回值改为了 String
     * 3. 声明抛出了两个异常,用于测试反射获取异常类型
     */
    private String eat(String something) throws IOException, NullPointerException {
        System.out.println("在吃" + something);
        return "奥利给";
    }

    /**
     * 重载的 private 方法
     */
    private void eat(String something, int a) {
        System.out.println("在吃" + something);
    }

    // ================= 公共的 Getters / Setters =================

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

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

总结Method.invoke 是反射从"结构读取"进入"行为执行"的关键步骤。它使得方法调用不再依赖编译期的直接调用语句,而可以由运行期决策产生。


1.8 核心规律总结:反射的通用内省 API

学完了 Constructor(构造方法)、Field(成员变量)、Method(成员方法)的获取与操作后,我们来做一个规律总结。

在这三个核心组件的示例代码中,我们反复用到了 getModifiers()getName()?这是因为无论类里面写的是什么,它们都有一些共通的元数据特征(比如名字和权限修饰符)。

因此,Java 为 ClassConstructorFieldMethod 提供了一套通用的底层 API 来提取这些信息。

1.8.1 通用核心 API

方法声明 功能核心说明 适用对象
int getModifiers() 获取该成员的所有修饰符集合(返回一个底层二进制标识整数) Class, Constructor, Field, Method
String getName() 获取该成员的名称(如类名、变量名、方法名) Class, Constructor, Field, Method
Class<?> getDeclaringClass() 获取声明该成员的类(即这个变量或方法是写在哪个类里面的) Constructor, Field, Method

1.8.2 深度解析:为什么 getModifiers() 返回的是整数?

当你调用 getModifiers() 时,返回的并不是我们想象中的 "public""private" 字符串,而是一个整数 (例如 129 等)。

底层原理:位掩码 (Bitmask)

Java 为了极度节省内存和提高判断效率,使用了二进制的位(0或1) 来表示修饰符。底层的 java.lang.reflect.Modifier 类中定义了如下常量:

  • PUBLIC = 1 (二进制: 0000 0001)
  • PRIVATE = 2 (二进制: 0000 0010)
  • PROTECTED = 4 (二进制: 0000 0100)
  • STATIC = 8 (二进制: 0000 1000)
  • FINAL = 16 (二进制: 0001 0000)

叠加态 :如果一个变量是 public static,它的值就是 1 + 8 = 9

这种设计让计算机可以通过极其高效的位运算 (按位与 &)来瞬间判断它是否包含了某个修饰符。

为了把枯燥的整数还原成人类可读的字符串,或者进行快速判断,我们必须配合 Modifier 工具类来使用。

Modifier 工具类常用方法 功能核心说明
static String toString(int mod) 将整数翻译为字符串 (例如传入 9,返回 "public static"
static boolean isPublic(int mod) 判断该整数中是否包含 public 修饰符
static boolean isPrivate(int mod) 判断该整数中是否包含 private 修饰符
static boolean isStatic(int mod) 判断该整数中是否包含 static 修饰符

1.8.3 代码示例



1.8.3.1 TestEntity
java 复制代码
package reflection.Replenish;

/**
 * 测试用的实体类,故意添加多个修饰符用于反射解析测试
 */
public class TestEntity {
    
    // 故意加上多个修饰符进行测试 (public = 1, static = 8, final = 16)
    public static final String GREETING = "Hello World";
    
    private int age;
}

1.8.3.2 ModifierReflectDemo
java 复制代码
package reflection.Replenish;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * 反射演示类:利用 Modifier 工具类深度解析权限修饰符
 */
public class ModifierReflectDemo {

    public static void main(String[] args) throws Exception {

        // 1. 获取 Class 对象
        Class<?> clazz = Class.forName("reflection.Replenish.TestEntity");

        // 2. 获取目标字段:public static final String GREETING
        Field field = clazz.getDeclaredField("GREETING");

        // ================= 解析修饰符 =================
        
        // 3.1 获取原始整数值
        int modifiers = field.getModifiers();
        System.out.println("1. 底层返回的整数值: " + modifiers); 
        // 预期输出: 25 (因为 public=1 + static=8 + final=16)

        // 3.2 将整数翻译为直观的字符串 (最常用)
        String modifierStr = Modifier.toString(modifiers);
        System.out.println("2. 翻译后的文本内容: " + modifierStr);
        // 预期输出: public static final

        // 3.3 精准判断是否包含某个特定修饰符
        boolean isStatic = Modifier.isStatic(modifiers);
        boolean isPrivate = Modifier.isPrivate(modifiers);
        
        System.out.println("3. 该字段是否被 static 修饰? " + isStatic);   // 预期: true
        System.out.println("4. 该字段是否被 private 修饰? " + isPrivate); // 预期: false
        
        // ================= 获取其他通用元数据 =================
        System.out.println("5. 该成员的名字是: " + field.getName());
        System.out.println("6. 声明该成员的类是: " + field.getDeclaringClass().getName());
    }
}

总结 :以后在做反射开发(如编写自动生成文档的工具、代码规范检查器等)时,只要拿到 getModifiers() 的返回值,第一时间把它扔给 Modifier.toString(modifiers) 就可以了。


1.9 反射综合练习1:保存对象信息

1.9.1 场景说明

该练习的核心目标是:对于任意一个对象,都可以把对象内部所有字段名和字段值保存到文件中

这一场景能够体现反射的通用性:程序不需要提前知道传入对象到底是 Student 还是 Teacher,只要通过 obj.getClass() 获取真实类型,就可以继续读取其字段结构。


1.9.2 代码示例



1.9.2.1 Student
java 复制代码
package reflection.five;

/**
 * 学生实体类,标准 JavaBean
 */
public class Student {
    private String name;
    private int age;
    private char gender;
    private double height;
    private String hobby;

    public Student() {
    }

    public Student(String name, int age, char gender, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.hobby = hobby;
    }

    // --- Getters & Setters ---
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public char getGender() { return gender; }
    public void setGender(char gender) { this.gender = gender; }
    public double getHeight() { return height; }
    public void setHeight(double height) { this.height = height; }
    public String getHobby() { return hobby; }
    public void setHobby(String hobby) { this.hobby = hobby; }

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

1.9.2.2 Teacher
java 复制代码
package reflection.five;

/**
 * 老师实体类,标准 JavaBean
 */
public class Teacher {
    private String name;
    private double salary;

    public Teacher() {
    }

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    // --- Getters & Setters ---
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }

    @Override
    public String toString() {
        return "Teacher{name='" + name + "', salary=" + salary + "}";
    }
}
1.9.2.3 MyReflectDemo
java 复制代码
package reflection.five;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;

/**
 * 反射综合练习:
 * 对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去。
 */
public class MyReflectDemo {

    public static void main(String[] args) throws IllegalAccessException, IOException {
        
        // 1. 准备测试数据
        Student s = new Student("小A", 23, '女', 167.5, "睡觉");
        Teacher t = new Teacher("播妞", 10000);

        // 2. 调用通用方法,保存对象信息
        saveObject(s);
        saveObject(t);
        
        System.out.println("数据保存成功,请查看本地文件!");
    }

    /**
     * 把对象里面所有的成员变量名和值保存到本地文件中
     *
     * @param obj 任意对象
     * @throws IllegalAccessException 反射获取私有变量值时可能抛出的异常
     * @throws IOException            IO 流写出数据时可能抛出的异常
     */
    public static void saveObject(Object obj) throws IllegalAccessException, IOException {
        
        // 1. 获取字节码文件的对象
        // 因为传入的是具体的对象,所以使用 对象.getClass() 方式最合适
        Class<?> clazz = obj.getClass();

        // 2. 创建 IO 流 (这里使用追加模式 true,防止第二个对象覆盖第一个对象的数据)
        // 注意:如果您的项目下没有 information 文件夹,请手动创建,或者直接写 "information.txt" 保存在项目根目录
        BufferedWriter bw = new BufferedWriter(new FileWriter("src\\reflection\\five\\information.txt", true));

        // 3. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            // 因为属性都是 private 的,必须暴力反射取消权限校验
            field.setAccessible(true);

            // 获取成员变量的名字 (如: name, age)
            String name = field.getName();

            // 获取该成员变量在传入对象 obj 中的具体值 (如: "小A", 23)
            Object value = field.get(obj);

            // 写出数据
            bw.write(name + "=" + value);
            bw.newLine();
        }

        // 4. 添加一个分隔符,方便区分不同对象的数据(优化点)
        bw.write("--------------------");
        bw.newLine();

        // 5. 释放资源
        bw.close();
    }
}

1.10 反射综合练习2:反射与配置文件结合动态创建

反射最典型的工程价值,是与配置文件结合使用。此时类名和方法名不再写死在源码中,而是由配置文件决定。


1.10.1 执行流程

  • 通过 Properties 加载配置文件。
  • 读取目标类名目标方法名
  • 通过 Class.forName 获取目标类的 Class 对象。
  • 通过构造方法创建目标对象。
  • 通过方法名获取 Method 对象。
  • 通过 invoke 执行目标方法。

1.10.2 代码示例



1.10.2.1 Student
java 复制代码
package reflection.six;

/**
 * 学生实体类
 */
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 学生特有的方法
     */
    public void study() {
        System.out.println("学生在学习!");
    }

    // --- Getters & Setters ---
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

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

1.10.2.2 Teacher
java 复制代码
package reflection.six;

/**
 * 老师实体类
 */
public class Teacher {
    private String name;
    private double salary;

    public Teacher() {
    }

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    /**
     * 老师特有的方法
     */
    public void teach() {
        System.out.println("老师在教书!");
    }

    // --- Getters & Setters ---
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }

    @Override
    public String toString() {
        return "Teacher{name='" + name + "', salary=" + salary + "}";
    }
}

1.10.2.3 prop.properties
properties 复制代码
classname=reflection.six.Student
method=study

配置文件说明

配置文件是整个程序的"动态控制中心"。我们彻底抛弃了在 Java 代码中硬编码 new 对象的方式,而是将类型信息提取到了这里:

  • classname :指定程序将要动态加载并实例化的全类名(包名 + 类名)。
  • method :指定对象实例化后,将要动态唤醒执行的方法名

框架的核心魅力 :如果你想让程序从"学生学习"变为执行"老师教书",完全不需要修改、也不需要重新编译任何 Java 源代码 。你只需将上述配置改为 classname=reflection.six.Teachermethod=teach,程序运行的行为就会彻底改变。这就是 Spring 等各大主流框架实现"解耦"和"可插拔"功能的底层基石。


1.10.2.4 MyReflectDemo
java 复制代码
package reflection.six;

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

/**
 * 反射核心应用演示:【配置文件 + 反射 = 框架的灵魂】
 * 目的:在不改变当前类任何代码的情况下,可以创建任意类的对象,并执行任意方法。
 */
public class MyReflectDemo {

    public static void main(String[] args) throws Exception {

        // ================= 1. 读取配置文件中的信息 =================
        Properties prop = new Properties();
        
        // 注意:这里的路径 "src\\prop.properties" 取决于你的项目结构。
        // 如果文件直接放在模块根目录下,可能只需要写 "prop.properties"
        FileInputStream fis = new FileInputStream("src\\prop.properties");
        prop.load(fis);
        fis.close();
        System.out.println("读取到的配置集合: " + prop);

        // ================= 2. 获取全类名和方法名 =================
        String className = (String) prop.get("classname");
        String methodName = (String) prop.get("method");
        
        System.out.println("目标类名: " + className);
        System.out.println("目标方法: " + methodName);

        // ================= 3. 利用反射创建对象并运行方法 =================
        
        // 3.1 获取目标类的字节码文件对象
        Class<?> clazz = Class.forName(className);

        // 3.2 获取无参构造方法,并实例化对象
        Constructor<?> con = clazz.getDeclaredConstructor();
        Object o = con.newInstance();
        System.out.println("反射创建的对象实例: " + o);

        // 3.3 获取配置文件中指定的成员方法
        Method method = clazz.getDeclaredMethod(methodName);
        
        // 习惯性动作:防止方法是 private 的,临时取消权限校验
        method.setAccessible(true);
        
        // 3.4 运行该方法
        System.out.print("执行方法的结果: ");
        method.invoke(o); 
    }
}

总结:配置文件负责描述"要创建谁、要调用谁",反射负责完成"创建与调用"。二者结合以后,程序具备了不修改源码即可替换实现的能力。


2. 动态代理

2.1 为什么需要代理

动态代理用于在不修改目标类源码的前提下对目标对象的方法进行增强或拦截

代理调用链可以抽象为:

也就是说,外部调用者并不直接接触真实对象,而是先经过代理对象。代理对象在转发调用之前,可以执行准备工作、权限校验、日志记录、性能统计等增强逻辑。


2.2 动态代理三要素

要素 说明
真正干活的对象 业务逻辑的真实执行者
代理对象 对外暴露的访问入口
通过代理调用方法 调用会先进入代理逻辑,再决定是否转发给真实对象

注意 :JDK 动态代理要求代理对象被代理对象 基于同一个接口。接口中定义的方法,就是代理能够增强或拦截的方法范围。


2.3 创建代理对象的核心 API

如何为 Java 对象创建一个代理对象?java.lang.reflect.Proxy 类提供了为对象产生代理对象的核心方法:

方法声明
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

核心参数解析:

  • 参数一 (loader) :用于指定用哪个类加载器,去加载生成的代理类。
  • 参数二 (interfaces) :指定接口。这些接口用于指定生成的代理长什么样,也就是它拥有哪些方法。
  • 参数三 (h) :调用处理程序。用来指定生成的代理对象要干什么事情(即具体的拦截和增强逻辑)。

2.4 代码示例



2.4.1 Star

java 复制代码
package reflection.seven;

/**
 * 明星接口
 * 我们需要把所有想要被代理的方法,统一定义在接口当中。
 */
public interface Star {

    /**
     * 唱歌方法
     *
     * @param name 歌曲名称
     * @return 感谢语
     */
    public abstract String sing(String name);

    /**
     * 跳舞方法
     */
    public abstract void dance();
}

2.4.2 BigStar

java 复制代码
package reflection.seven;

/**
 * 大明星类(目标类/被代理类)
 * 必须实现对应的接口。
 */
public class BigStar implements Star {
    
    private String name;

    public BigStar() {
    }

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

    // ================= 核心业务方法 =================

    @Override
    public String sing(String name) {
        System.out.println(this.name + " 正在唱 " + name);
        return "谢谢大家";
    }

    @Override
    public void dance() {
        System.out.println(this.name + " 正在跳舞");
    }

    // ================= Getters & Setters =================
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2.4.3 ProxyUtil

java 复制代码
package reflection.seven;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理工具类
 * 用于给指定的明星对象创建一个代理对象(即"经纪人")。
 */
public class ProxyUtil {

    /**
     * 给一个明星对象,创建一个代理对象
     *
     * @param bigStar 被代理的明星对象(被代理对象)
     * @return 给明星创建的代理对象(实现了 Star 接口)
     */
    public static Star createProxy(BigStar bigStar) {

        /*
         * java.lang.reflect.Proxy 类提供了为对象产生代理对象的方法:
         * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
         * * 参数一 (loader): 类加载器。用于加载动态生成的代理类。通常使用当前类的加载器即可。
         * * 参数二 (interfaces): 接口数组。指定生成的代理对象长什么样,也就是它需要具备哪些方法。
         * * 参数三 (h): 调用处理程序 (InvocationHandler)。用来指定代理对象在被调用方法时,具体要干什么事情。
         */
        
        Star starProxy = (Star) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(), // 参数一:类加载器
                new Class<?>[]{Star.class},       // 参数二:指定的接口 (注意:这里补全了泛型 <?> 消除警告)
                new InvocationHandler() {         // 参数三:具体要干的事情
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*
                         * invoke 方法的作用:
                         * 当外面调用代理对象的任何方法时,都会自动走到这个 invoke 方法里面来!
                         * * 参数解析:
                         * @param proxy  代理对象本身(通常在内部用不到)
                         * @param method 正在被调用的方法对象(比如 sing 或 dance)
                         * @param args   调用方法时传递的实际参数数组
                         */

                        // 1. 代理对象进行前置拦截(比如:收钱、准备场地)
                        if ("sing".equals(method.getName())) {
                            System.out.println("【代理人拦截】准备话筒,收钱!");
                        } else if ("dance".equals(method.getName())) {
                            System.out.println("【代理人拦截】准备场地,收钱!");
                        }

                        // 2. 核心逻辑:去找真正的大明星开始唱歌或者跳舞
                        // 代码表现形式:利用反射,调用大明星(bigStar)里对应的方法
                        Object result = method.invoke(bigStar, args);

                        // 3. 将大明星方法的返回值,原封不动地返回给外部调用者
                        return result;
                    }
                }
        );

        return starProxy;
    }
}

2.4.4 Test

java 复制代码
package reflection.seven;

/**
 * 测试类
 * 需求:外面的人想要请大明星唱一首歌或者跳一段舞。
 */
public class Test {

    public static void main(String[] args) {

        // 1. 创建被代理的对象(大明星本人)
        BigStar bigStar = new BigStar("鸡哥");

        // 2. 获取该对象的代理对象(找经纪人)
        // 注意:代理对象实现了和目标对象相同的接口,所以可以用 Star 接口来接收
        Star proxy = ProxyUtil.createProxy(bigStar);

        // 3. 调用代理对象的唱歌方法
        // 过程:外部调用 sing -> 触发 InvocationHandler 的 invoke -> 打印"准备话筒收钱" -> 反射执行真正的 sing -> 返回"谢谢"
        System.out.println("--- 测试唱歌 ---");
        String result = proxy.sing("只因你太美");
        System.out.println("方法返回值: " + result);

        // 4. 调用代理对象的跳舞方法
        System.out.println("\n--- 测试跳舞 ---");
        proxy.dance();
    }
}

2.5 核心逻辑深度解析

动态代理最核心的执行点是 InvocationHandler 中的 invoke 方法。当代理对象被创建后,外部对它的每一次方法调用,都会被强制转发到该 invoke 方法中

代理与反射的结合:

代理对象内部高度依赖反射。代理对象接收到的并不是一个普通的方法调用语句,而是一个元数据 Method 对象。

  • Method method:代表当前外部正在调用的方法。
  • Object[] args:代表调用该方法时传入的实际参数。
  • method.invoke(bigStar, args):代表代理对象自己干完活(拦截增强)后,利用反射把请求"放行"给真正的大明星。

2.6 反射与动态代理的关系

动态代理可以看作是反射在"方法调用增强"领域的一种高级应用模式。

核心关系梳理:

  1. 没有反射就没有动态代理 :如果不利用反射(Method.invoke),代理对象根本无法在不知道目标类型的情况下动态转发方法。
  2. 职责分工明确:反射赋予了程序在运行期解析和执行的能力;而动态代理利用这个能力,构建了一道防火墙/增强环,把非核心业务逻辑(如日志、事务、权限)从核心业务代码中剥离了出来。

2.7 模块小结

知识点 核心结论
Class 反射体系的入口,表示运行期类型信息
Constructor 用于动态创建对象
Field 用于动态读取和修改成员变量
Method 用于动态调用成员方法
配置文件结合反射 使程序不修改源码也能替换目标类和方法
动态代理 在不修改目标类的前提下增强或拦截方法
JDK 动态代理限制 必须基于接口生成代理对象

最终结论:反射解决的是"运行期如何识别并操作类结构"的问题,动态代理解决的是"运行期如何增强对象行为"的问题。二者共同构成了 Java 后端框架(如 Spring AOP)中配置驱动、自动装配、方法增强等机制的最底层技术基础。

相关推荐
woniu_buhui_fei14 小时前
分布式限流
java·分布式
D4c-lovetrain14 小时前
Jenkins 实战:Java 项目全自动打包、镜像构建、K8s 集群部署(完整CI/CD方案)
java·kubernetes·jenkins
摇滚侠14 小时前
阿里云镜像站 CentOS Tomcat Maven 等镜像资源
java·阿里云·centos
身如柳絮随风扬14 小时前
ArrayList vs LinkedList:底层原理、性能对决与扩容机制全解析
java
超梦dasgg14 小时前
归并排序 Java 实现(递归 + 非递归)
java·算法·排序算法
我是一颗柠檬15 小时前
【JDK8新特性】JDK8实战与面试高频考点汇总Day12
java·开发语言·后端·面试·职场和发展
疯狂成瘾者15 小时前
常见的优化查询速度方法
java
YOU OU15 小时前
Spring事务和事务传播机制
java·数据库·spring
Chase_______15 小时前
【Java基础】5 / 2 为什么等于 2?整数除法、取余和 floorMod 一次讲清
java·开发语言