Java 基础语法详解(八)

Java 基础语法详解(八)

二十一、反射(Reflection)基础

反射是 Java 的核心特性之一,允许程序在运行时获取类的完整结构(如类名、属性、方法、构造器),并动态创建对象、调用方法、修改属性,无需在编译时确定具体类。反射是框架(如 Spring、MyBatis)实现 “解耦” 和 “自动化” 的核心技术,也常与注解结合使用。

1. 反射的核心作用

  • 动态获取类信息:运行时查看类的属性、方法、构造器等结构,无需提前知道类名;

  • 动态创建对象:通过类的全限定名(如com.example.Student)创建对象,无需new关键字;

  • 动态操作成员:运行时调用类的方法(包括私有方法)、修改成员变量(包括私有变量);

  • 框架底层支撑:Spring 的 IOC 容器、MyBatis 的 SQL 映射,均通过反射实现类的动态加载和调用。

2. 反射的核心 API(java.lang.reflect 包)

反射的核心操作围绕Class类展开,Class是 “类的类”,每个类在 JVM 中都对应一个唯一的Class对象,通过它可获取类的所有信息。常用 API 如下:

核心类 / 接口主要功能
Class代表类的字节码对象,提供获取类名、属性、方法、构造器的方法(如getFields()、getMethods())
Constructor代表类的构造方法,用于动态创建对象(如newInstance())
Field代表类的成员变量,用于获取 / 修改属性值(如get()、set())
Method代表类的成员方法,用于动态调用方法(如invoke())

3. 反射的常用操作

(1)获取Class对象的 3 种方式

Class对象是反射的入口,获取方式有 3 种,适用于不同场景:

public class GetClassObjectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式1:通过类名.class(编译时已知类,最常用)
        Class<Student> class1 = Student.class;
        System.out.println("方式1:" + class1.getName()); // 输出:方式1:com.example.Student

        // 方式2:通过对象.getClass()(已有对象实例,运行时获取)
        Student student = new Student("张三", 20);
        Class<? extends Student> class2 = student.getClass();
        System.out.println("方式2:" + class2.getName()); // 输出:方式2:com.example.Student

        // 方式3:通过Class.forName("类的全限定名")(编译时未知类,动态加载,需处理异常)
        Class<?> class3 = Class.forName("com.example.Student");
        System.out.println("方式3:" + class3.getName()); // 输出:方式3:com.example.Student

        // 验证:同一个类的Class对象唯一(JVM中仅加载一次)
        System.out.println("class1 == class2:" + (class1 == class2)); // 输出:true
        System.out.println("class1 == class3:" + (class1 == class3)); // 输出:true
    }
}

// 定义Student类(用于测试)
class Student {
    private String name;
    public int age;

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

    // 有参数构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 成员方法(public)
    public String introduce() {
        return "我是" + name + ",今年" + age + "岁";
    }

    // 私有成员方法
    private void study(String subject) {
        System.out.println(name + "正在学习" + subject);
    }

    // getter/setter(访问私有属性name)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
(2)动态创建对象(通过Constructor)

通过反射调用类的构造方法创建对象,支持无参数和有参数构造,包括私有构造(需设置setAccessible(true)突破访问限制)。

import java.lang.reflect.Constructor;

public class ReflectNewObjectDemo {
    public static void main(String[] args) throws Exception {
        // 1. 获取Student类的Class对象
        Class<?> studentClass = Class.forName("com.example.Student");

        // 方式1:调用无参数构造方法(推荐,若类有无参构造)
        Object student1 = studentClass.newInstance(); // JDK 9+已过时,推荐用Constructor
        System.out.println("方式1创建的对象:" + student1); // 输出:方式1创建的对象:com.example.Student@1b6d3586

        // 方式2:调用有参数构造方法(需指定参数类型)
        // 2.1 获取有参数构造器(参数类型:String.class, int.class)
        Constructor<?> constructor = studentClass.getConstructor(String.class, int.class);
        // 2.2 调用构造器创建对象(传入参数值)
        Object student2 = constructor.newInstance("李四", 19);
        Student s2 = (Student) student2; // 强制转换(已知类型时)
        System.out.println("方式2创建的对象:" + s2.introduce()); // 输出:方式2创建的对象:我是李四,今年19岁

        // 方式3:调用私有构造方法(需突破访问限制)
        // 3.1 获取私有构造器(getDeclaredConstructor()获取所有构造器,包括私有)
        Constructor<?> privateConstructor = studentClass.getDeclaredConstructor(String.class);
        // 3.2 设置允许访问私有构造器(关键:setAccessible(true))
        privateConstructor.setAccessible(true);
        // 3.3 创建对象
        Object student3 = privateConstructor.newInstance("王五");
        Student s3 = (Student) student3;
        s3.age = 21; // 设置public属性
        System.out.println("方式3创建的对象:" + s3.introduce()); // 输出:方式3创建的对象:我是王五,今年21岁
    }
}

// 补充Student类的私有构造方法(用于测试方式3)
class Student {
    // ... 原有代码 ...

    // 私有构造方法(仅接收name)
    private Student(String name) {
        this.name = name;
    }
}
(3)动态操作成员变量(通过Field)

通过反射获取类的成员变量(包括私有变量),并获取 / 修改其值,私有变量需设置setAccessible(true)。

import java.lang.reflect.Field;

public class ReflectFieldDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建Student对象(普通方式)
        Student student = new Student("赵六", 22);
        // 2. 获取Class对象
        Class<? extends Student> studentClass = student.getClass();

        // 方式1:操作public成员变量(age)
        // 2.1 获取public属性(getField()仅获取public属性)
        Field ageField = studentClass.getField("age");
        // 2.2 获取属性值
        int age = (int) ageField.get(student);
        System.out.println("修改前age:" + age); // 输出:修改前age:22
        // 2.3 修改属性值
        ageField.set(student, 23);
        System.out.println("修改后age:" + student.age); // 输出:修改后age:23

        // 方式2:操作private成员变量(name)
        // 2.1 获取private属性(getDeclaredField()获取所有属性,包括私有)
        Field nameField = studentClass.getDeclaredField("name");
        // 2.2 允许访问私有属性
        nameField.setAccessible(true);
        // 2.3 获取属性值
        String name = (String) nameField.get(student);
        System.out.println("修改前name:" + name); // 输出:修改前name:赵六
        // 2.4 修改属性值
        nameField.set(student, "钱七");
        System.out.println("修改后name:" + student.getName()); // 输出:修改后name:钱七
    }
}
(4)动态调用成员方法(通过Method)

通过反射调用类的成员方法(包括私有方法),支持无参数和有参数方法,私有方法需设置setAccessible(true)。

import java.lang.reflect.Method;

public class ReflectMethodDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建Student对象
        Student student = new Student("孙八", 20);
        // 2. 获取Class对象
        Class<? extends Student> studentClass = student.getClass();

        // 方式1:调用public无参数方法(introduce())
        // 2.1 获取public方法(参数1:方法名,参数2:方法参数类型(无参数则不写))
        Method introduceMethod = studentClass.getMethod("introduce");
        // 2.2 调用方法(参数1:对象实例,参数2:方法参数值(无参数则不写))
        String result = (String) introduceMethod.invoke(student);
        System.out.println("调用introduce()结果:" + result); // 输出:调用introduce()结果:我是孙八,今年20岁

        // 方式2:调用private有参数方法(study(String subject))
        // 2.1 获取private方法(getDeclaredMethod()获取所有方法,包括私有)
        Method studyMethod = studentClass.getDeclaredMethod("study", String.class);
        // 2.2 允许访问私有方法
        studyMethod.setAccessible(true);
        // 2.3 调用方法(传入参数值:"Java")
        studyMethod.invoke(student, "Java"); // 输出:孙八正在学习Java
    }
}

二十二、反射与注解的协同应用

反射与注解是 “黄金搭档”:注解提供 “元数据标记”,反射提供 “运行时解析元数据” 的能力,两者结合可实现灵活的业务逻辑(如参数校验、权限控制)。以下通过 “参数校验注解” 案例,展示两者的协同工作流程。

案例:自定义 “非空校验注解”

需求:定义@NotNull注解,标记成员变量 “不允许为 null”,通过反射解析注解,在对象初始化后自动校验属性值,若为 null 则抛异常。

1. 定义自定义注解@NotNull
import java.lang.annotation.*;

// 自定义非空校验注解
@Target(ElementType.FIELD) // 仅作用于成员变量
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,支持反射解析
public @interface NotNull {
    // 注解属性:校验失败时的提示信息(默认值为“字段不能为空”)
    String message() default "字段不能为空";
}
2. 定义实体类(使用@NotNull注解)
// 用户实体类:用@NotNull标记关键属性
class User {
    @NotNull(message = "用户名不能为空") // 标记name不允许为null
    private String name;

    @NotNull(message = "用户ID不能为空") // 标记id不允许为null
    private Integer id;

    private Integer age; // 未标记,允许为null

    // 构造方法
    public User(String name, Integer id, Integer age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    // getter/setter
    public String getName() {
        return name;
    }

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

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
3. 实现注解解析器(反射 + 注解校验)
import java.lang.reflect.Field;

// 注解解析器:校验对象中被@NotNull标记的属性
public class NotNullValidator {
    // 校验方法:接收任意对象,返回校验结果(无异常则校验通过)
    public static void validate(Object obj) throws IllegalAccessException {
        if (obj == null) {
            throw new IllegalArgumentException("待校验对象不能为空");
        }

        // 1. 获取对象的Class对象
        Class<?> objClass = obj.getClass();
        // 2. 获取对象的所有成员变量(包括私有)
        Field[] fields = objClass.getDeclaredFields();

        // 3. 遍历所有属性,检查是否有@NotNull注解
        for (Field field : fields) {
            // 3.1 判断属性是否被@NotNull标记
            if (field.isAnnotationPresent(NotNull.class)) {
                // 3.2 允许访问私有属性
                field.setAccessible(true);
                // 3.3 获取属性值
                Object fieldValue = field.get(obj);
                // 3.4 校验:若属性值为null,抛异常(提示信息取自注解)
                if (fieldValue == null) {
                    NotNull notNullAnnotation = field.getAnnotation(NotNull.class);
                    String message = notNullAnnotation.message();
                    throw new NullPointerException("校验失败:" + message + "(字段名:" + field.getName() + ")");
                }
            }
        }

        System.out.println("所有@NotNull字段校验通过!");
    }

    // 测试校验逻辑
    public static void main(String[] args) throws IllegalAccessException {
        // 测试1:正常对象(name和id均不为null)
        User validUser = new User("Alice", 1001, 25);
        validate(validUser); // 输出:所有@NotNull字段校验通过!

        // 测试2:异常对象(name为null)
        User invalidUser1 = new User(null, 1002, 26);
        try {
            validate(invalidUser1);
        } catch (NullPointerException e) {
            System.out.println(e.getMessage()); // 输出:校验失败:用户名不能为空(字段名:name)
        }

        // 测试3:异常对象(id为null)
        User invalidUser2 = new User("Bob", null, 27);
        try {
            validate(invalidUser2);
        } catch (NullPointerException e) {
            System.out.println(e.getMessage()); // 输出:校验失败:用户ID不能为空(字段名:id)
        }
    }
}

输出结果

所有@NotNull字段校验通过!
校验失败:用户名不能为空(字段名:name)
校验失败:用户ID不能为空(字段名:id)

核心逻辑

  1. 注解@NotNull标记需要校验的属性,携带 “校验失败提示信息”;

  2. 解析器NotNullValidator通过反射获取对象的所有属性,检查是否有@NotNull注解;

  3. 若有注解且属性值为 null,通过反射获取注解的message属性,抛出自定义异常;

  4. 该逻辑可复用(如校验Order、Product等任意实体类),体现 “注解标记 + 反射解析” 的灵活性。

二十三、Java 基础语法实战案例(综合应用)

为巩固所学基础语法,以下通过 “简易学生管理系统” 案例,综合应用类与对象、集合、枚举、注解、反射等知识,实现 “添加学生、查询学生、删除学生、校验学生信息” 的核心功能。

1. 案例需求

  • 支持添加学生(需校验姓名、学号不为 null,年龄合法);

  • 支持根据学号查询学生;

  • 支持根据学号删除学生;

  • 支持遍历所有学生信息;

  • 学生状态分为 “在读”“毕业”“休学”(用枚举表示)。

2. 代码实现

(1)定义枚举(学生状态)
// 学生状态枚举
enum StudentStatus {
    STUDYING("在读"), GRADUATED("毕业"), SUSPENDED("休学");

    private final String desc;

    StudentStatus(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}
(2)定义校验注解(非空、年龄范围)
import java.lang.annotation.*;

// 非空校验注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值