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)
核心逻辑:
-
注解@NotNull标记需要校验的属性,携带 “校验失败提示信息”;
-
解析器NotNullValidator通过反射获取对象的所有属性,检查是否有@NotNull注解;
-
若有注解且属性值为 null,通过反射获取注解的message属性,抛出自定义异常;
-
该逻辑可复用(如校验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


被折叠的 条评论
为什么被折叠?



