javaSE泛型、反射与注解的核心原理与实际应用
作者:peachcobbler
前言
泛型、反射与注解是 JavaSE 中支撑 “灵活编程” 与 “框架设计” 的核心技术。泛型解决 “类型安全” 问题,反射实现 “运行时动态操作类”,注解提供 “代码标记与元数据” 能力 —— 三者结合构成了 Java 框架(如 Spring、MyBatis)的底层基础。本章节将系统讲解这三项技术的核心原理与实际应用。
一、泛型(Generic):编译时的类型安全保障
在泛型出现之前,集合(如List)默认存储Object类型,取出元素时需强制转换,容易出现ClassCastException(类型转换异常)。泛型通过 “编译时类型指定”,让集合只能存储特定类型元素,从源头避免类型错误。
1.1 泛型的核心作用
- 编译时类型检查:限制集合(或泛型类)只能存储指定类型元素,编译阶段就报错,而非运行时崩溃。
- 避免强制转换:取出元素时无需手动转换(编译器自动确认类型)。
- 代码复用:一套逻辑支持多种类型(如
List<String>、List<Integer>共用List的实现)。
1.2 泛型的基本用法
1.2.1 泛型类与泛型接口
泛型类 / 接口在定义时声明 “类型参数”(如<T>),使用时指定具体类型(如String)。
泛型类示例:
// 定义泛型类:声明类型参数T(Type的缩写,可自定义名称)
class GenericBox<T> {
// 使用T作为类型(类似变量,代表一种类型)
private T value;
// T作为方法参数和返回值
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class GenericClassDemo {
public static void main(String[] args) {
// 使用时指定类型为String(只能存String)
GenericBox<String> stringBox = new GenericBox<>();
stringBox.setValue("Hello"); // 正确:存入String
// stringBox.setValue(123); // 编译错误:不能存Integer
// 取出时无需转换(自动为String类型)
String str = stringBox.getValue();
System.out.println(str); // 输出:Hello
// 指定类型为Integer
GenericBox<Integer> intBox = new GenericBox<>();
intBox.setValue(100);
Integer num = intBox.getValue(); // 无需转换
System.out.println(num); // 输出:100
}
}
泛型接口示例:
// 定义泛型接口(支持多种类型的“生产者”)
interface Producer<T> {
T produce();
}
// 实现泛型接口时指定具体类型(如String)
class StringProducer implements Producer<String> {
@Override
public String produce() {
return "生产的字符串";
}
}
// 实现时保留泛型(让子类也成为泛型类)
class NumberProducer<T extends Number> implements Producer<T> {
private T value;
public NumberProducer(T value) {
this.value = value;
}
@Override
public T produce() {
return value;
}
}
public class GenericInterfaceDemo {
public static void main(String[] args) {
Producer<String> strProducer = new StringProducer();
String str = strProducer.produce();
System.out.println(str); // 输出:生产的字符串
Producer<Integer> intProducer = new NumberProducer<>(100);
Integer num = intProducer.produce();
System.out.println(num); // 输出:100
}
}
1.2.2 泛型方法
泛型方法在方法声明时独立声明类型参数(与类是否泛型无关),适用于 “单个方法需要支持多种类型” 的场景。
class GenericMethodDemo {
// 定义泛型方法:<E>是方法的类型参数,声明在返回值前
public <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// 泛型方法带返回值
public <E> E getFirstElement(E[] array) {
if (array != null && array.length > 0) {
return array[0];
}
return null;
}
}
public class TestGenericMethod {
public static void main(String[] args) {
GenericMethodDemo demo = new GenericMethodDemo();
// 调用时自动推断类型(无需显式指定)
String[] strArray = {"A", "B", "C"};
demo.printArray(strArray); // 输出:A B C
Integer[] intArray = {1, 2, 3};
demo.printArray(intArray); // 输出:1 2 3
// 获取第一个元素(自动返回对应类型)
String firstStr = demo.getFirstElement(strArray);
Integer firstInt = demo.getFirstElement(intArray);
System.out.println("字符串数组第一个元素:" + firstStr); // 输出:A
System.out.println("整数数组第一个元素:" + firstInt); // 输出:1
}
}
泛型方法特点:
- 类型参数声明在方法返回值前(如
<E>),与类的泛型参数无关。- 调用时编译器自动推断类型(无需手动指定,如传入
String[]则E自动为String)。
1.2.3 类型通配符(Wildcard)
当需要处理 “未知类型的泛型” 时(如方法参数需要接收任意泛型List),使用通配符?及限定符(extends、super)控制类型范围。
| 通配符形式 | 含义 | 适用场景 |
|---|---|---|
<?> | 任意类型(无限制) | 仅读取元素,不修改(如打印任意 List) |
<? extends T> | 上限:只能是 T 或 T 的子类 | 读取(可获取 T 类型),不能添加(除 null) |
<? super T> | 下限:只能是 T 或 T 的父类 | 添加(可添加 T 或子类),读取只能到 Object |
通配符示例:
import java.util.ArrayList;
import java.util.List;
public class WildcardDemo {
// 1. 无限制通配符<?>:接收任意List,只能读,不能写(除null)
public static void printList(List<?> list) {
for (Object obj : list) { // 只能用Object接收
System.out.print(obj + " ");
}
System.out.println();
// list.add("A"); // 编译错误:无法确定类型,不能添加非null元素
}
// 2. 上限通配符<? extends Number>:只能接收Number或其子类(如Integer、Double)
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number num : list) { // 可安全转为Number
total += num.doubleValue(); // 调用Number的方法
}
return total;
}
// 3. 下限通配符<? super Integer>:只能接收Integer或其父类(如Number、Object)
public static void addIntegers(List<? super Integer> list) {
list.add(10); // 可添加Integer(或其子类,如Integer本身)
list.add(20);
// Integer num = list.get(0); // 编译错误:只能用Object接收
}
public static void main(String[] args) {
// 测试<?>
List<String> strList = List.of("A", "B");
List<Integer> intList = List.of(1, 2);
printList(strList); // 输出:A B
printList(intList); // 输出:1 2
// 测试<? extends Number>
List<Integer> integerList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.5, 2.5);
System.out.println("整数和:" + sum(integerList)); // 输出:6.0
System.out.println("小数和:" + sum(doubleList)); // 输出:4.0
// 测试<? super Integer>
List<Number> numberList = new ArrayList<>();
addIntegers(numberList); // 向NumberList添加Integer
System.out.println("添加后:" + numberList); // 输出:[10, 20]
}
}
1.3 泛型的局限
- 不能用基本类型:泛型参数只能是引用类型(如
List<int>错误,需用List<Integer>)。 - 运行时擦除:泛型信息在编译后被擦除(如
List<String>和List<Integer>运行时都是List),无法通过instanceof判断泛型类型。 - 静态方法不能用类的泛型参数:静态方法属于类,而泛型参数随对象变化,若需泛型需定义为泛型方法。
二、反射(Reflection):运行时的类信息操作
反射允许程序在运行时获取类的信息(如类名、属性、方法、构造器),并动态操作这些成分(如调用私有方法、修改私有属性)。这打破了 “编译时确定代码逻辑” 的限制,让程序更灵活(也是框架实现 “自动装配”“依赖注入” 的核心)。
2.1 反射的核心作用
- 运行时获取类信息:无需提前知道类名,就能获取类的属性、方法等元数据。
- 动态创建对象:通过类信息动态实例化对象(如
Class.newInstance())。- 动态调用方法:包括私有方法(通过反射可绕过访问权限)。
- 动态操作属性:包括私有属性(可修改值)。
2.2 反射的核心类
反射的所有操作都基于java.lang.Class类(类对象),它是反射的 “入口”。
| 核心类 / 接口 | 作用 |
|---|---|
Class | 类的元数据对象,代表一个类的信息 |
Constructor | 类的构造器对象,用于创建实例 |
Method | 类的方法对象,用于调用方法 |
Field | 类的属性对象,用于访问 / 修改属性值 |
2.3 反射的基本操作
2.3.1 获取 Class 对象(反射入口)
获取Class对象有 3 种方式,根据场景选择:
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(name + "正在学习");
}
private String getInfo() {
return "姓名:" + name + ",年龄:" + age;
}
}
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:通过对象.getClass()(已知对象)
Student student = new Student();
Class<?> clazz1 = student.getClass();
System.out.println("方式1:" + clazz1.getName()); // 输出:Student
// 方式2:通过类名.class(已知类名,编译时确定)
Class<?> clazz2 = Student.class;
System.out.println("方式2:" + clazz2.getName()); // 输出:Student
// 方式3:通过Class.forName("全类名")(仅知类名,运行时动态获取,最常用)
Class<?> clazz3 = Class.forName("Student"); // 全类名:包名+类名(此处默认无包)
System.out.println("方式3:" + clazz3.getName()); // 输出:Student
// 验证:同一个类的Class对象唯一
System.out.println(clazz1 == clazz2); // 输出:true
System.out.println(clazz1 == clazz3); // 输出:true
}
}
说明:一个类的Class对象在 JVM 中唯一,是类加载的产物(类加载时 JVM 自动创建Class对象)。
2.3.2 反射创建对象(通过构造器)
通过Class对象获取Constructor,再调用newInstance()创建实例(支持无参和有参构造)。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectNewInstance {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
// 1. 获取Class对象
Class<?> clazz = Class.forName("Student");
// 2. 方式1:调用无参构造(若类无无参构造,会抛异常)
Student student1 = (Student) clazz.newInstance(); // 已过时,推荐用Constructor
System.out.println("无参构造创建:" + student1);
// 3. 方式2:调用有参构造(更灵活,推荐)
// 获取有参构造器(参数为String和int)
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 传入参数创建实例
Student student2 = (Student) constructor.newInstance("张三", 18);
System.out.println("有参构造创建:" + student2);
}
}
2.3.3 反射调用方法(包括私有方法)
通过Method对象调用方法,支持公有和私有方法(私有方法需先设置setAccessible(true)取消访问检查)。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectInvokeMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> clazz = Class.forName("Student");
Student student = (Student) clazz.getConstructor(String.class, int.class).newInstance("张三", 18);
// 1. 调用公有方法(study())
// 获取方法:参数1为方法名,参数2为参数类型(无参则不写)
Method studyMethod = clazz.getMethod("study");
// 调用方法:参数1为实例对象,参数2为方法参数(无参则不写)
studyMethod.invoke(student); // 输出:张三正在学习
// 2. 调用私有方法(getInfo())
// 获取私有方法需用getDeclaredMethod(getMethod只能获取公有)
Method getInfoMethod = clazz.getDeclaredMethod("getInfo");
// 取消访问检查(关键:私有方法必须设置,否则抛异常)
getInfoMethod.setAccessible(true);
// 调用私有方法
String info = (String) getInfoMethod.invoke(student);
System.out.println("私有方法返回:" + info); // 输出:姓名:张三,年龄:18
}
}
2.3.4 反射操作属性(包括私有属性)
通过Field对象访问或修改属性,私有属性同样需要setAccessible(true)。
import java.lang.reflect.Field;
public class ReflectOperateField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = Class.forName("Student");
Student student = (Student) clazz.getConstructor().newInstance(); // 无参构造创建
// 1. 获取并修改私有属性name
Field nameField = clazz.getDeclaredField("name"); // 获取私有属性
nameField.setAccessible(true); // 取消访问检查
nameField.set(student, "李四"); // 设置属性值(参数1为实例,参数2为值)
// 2. 获取并修改私有属性age
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(student, 20);
// 验证修改结果(调用之前的私有方法getInfo())
Method getInfoMethod = clazz.getDeclaredMethod("getInfo");
getInfoMethod.setAccessible(true);
String info = (String) getInfoMethod.invoke(student);
System.out.println("修改后信息:" + info); // 输出:姓名:李四,年龄:20
}
}
2.4 反射的应用场景
- 框架底层:Spring 的 IOC 容器通过反射创建对象并注入依赖;MyBatis 通过反射将数据库结果集映射为 Java 对象。
- 注解解析:自定义注解需配合反射获取注解标记的类 / 方法,执行对应逻辑(如权限校验)。
- 动态代理:AOP 的动态代理(如 JDK 代理)基于反射实现方法增强。
- 工具类:如 JSON 序列化工具(Jackson、FastJSON)通过反射获取对象属性并转为 JSON。
2.5 反射的优缺点
- 优点:灵活性高,支持运行时动态操作,是框架的核心技术。
- 缺点:
- 性能损耗:反射操作绕开编译优化,性能比直接调用低(但框架中影响可接受)。
- 破坏封装:可直接访问私有成员,可能导致代码逻辑混乱。
- 可读性差:反射代码较繁琐,不如直接调用直观。
三、注解(Annotation):代码的标记与元数据
注解(Annotation)是 Java 5 引入的特性,本质是 “代码的标记”,可在类、方法、属性等元素上添加,用于携带 “元数据”(描述数据的数据)。注解本身不直接影响代码逻辑,但可通过反射解析注解,执行对应操作。
3.1 注解的核心作用
- 标记代码:如
@Override标记方法重写,编译器会校验是否符合重写规则。- 携带元数据:如
@Test标记测试方法,测试框架会自动执行标记的方法。- 简化配置:替代 XML 配置(如 Spring 的
@Controller标记控制器类)。
3.2 常用内置注解
Java 内置了 3 个基本注解(定义在java.lang包),编译器会识别并处理:
| 注解名称 | 作用 | 使用位置 |
|---|---|---|
@Override | 标记方法为重写父类的方法,编译器校验 | 方法 |
@Deprecated | 标记元素(类、方法等)已过时,编译器警告 | 类、方法、属性等 |
@SuppressWarnings | 抑制编译器警告(如未使用变量警告) | 类、方法等 |
内置注解示例:
public class BuiltInAnnotationDemo {
// @Deprecated:标记方法已过时
@Deprecated
public void oldMethod() {
System.out.println("这是过时的方法");
}
// @Override:标记方法重写(若父类无此方法,编译报错)
@Override
public String toString() {
return "BuiltInAnnotationDemo对象";
}
// @SuppressWarnings:抑制“未使用变量”警告
@SuppressWarnings("unused")
public void test() {
int unusedVar = 10; // 若没有@SuppressWarnings,编译器会警告“变量未使用”
// 调用过时方法(编译器会警告,但可执行)
oldMethod();
}
public static void main(String[] args) {
new BuiltInAnnotationDemo().test();
}
}
3.3 元注解:定义注解的注解
自定义注解时,需要用 “元注解”(注解的注解)指定注解的 “作用范围”“保留策略” 等。Java 提供 4 个元注解(定义在java.lang.annotation包):
| 元注解名称 | 作用 | 常用值 |
|---|---|---|
@Target | 指定注解可使用的位置(如方法、类) | ElementType.METHOD(方法)、ElementType.TYPE(类)等 |
@Retention | 指定注解的保留策略(生命周期) | RetentionPolicy.RUNTIME(运行时保留,可反射获取) |
@Documented | 标记注解会被 javadoc 文档记录 | 无参数 |
@Inherited | 标记注解可被子类继承 | 无参数 |
核心元注解:@Target和@Retention是自定义注解必须的 ——@Target限制使用位置,@Retention(RetentionPolicy.RUNTIME)确保注解在运行时存在(才能被反射解析)。
3.4 自定义注解及解析
自定义注解需配合反射使用:先定义注解,再在代码中标记,最后通过反射解析注解并执行逻辑。
示例:自定义权限校验注解
import java.lang.annotation.*;
import java.lang.reflect.Method;
// 1. 定义自定义注解
@Target(ElementType.METHOD) // 注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可反射获取
@interface RequirePermission {
// 注解属性(类似方法,可指定默认值)
String value(); // 权限名称(如"admin")
}
// 2. 使用注解标记方法
class UserService {
// 标记需要"admin"权限才能执行
@RequirePermission("admin")
public void deleteUser() {
System.out.println("执行删除用户操作");
}
// 标记需要"user"权限才能执行
@RequirePermission("user")
public void queryUser() {
System.out.println("执行查询用户操作");
}
}
// 3. 通过反射解析注解,实现权限校验
class PermissionChecker {
// 模拟当前用户拥有的权限
private String currentPermission = "admin";
// 执行方法前校验权限
public void executeWithCheck(Object obj, String methodName) throws Exception {
// 获取方法对象
Method method = obj.getClass().getMethod(methodName);
// 判断方法是否有@RequirePermission注解
if (method.isAnnotationPresent(RequirePermission.class)) {
// 获取注解对象
RequirePermission annotation = method.getAnnotation(RequirePermission.class);
// 获取注解的权限值
String requiredPerm = annotation.value();
// 校验权限
if (currentPermission.equals(requiredPerm)) {
method.invoke(obj); // 权限通过,执行方法
} else {
throw new RuntimeException("权限不足,需要:" + requiredPerm);
}
} else {
// 无注解,直接执行
method.invoke(obj);
}
}
}
// 测试
public class CustomAnnotationDemo {
public static void main(String[] args) throws Exception {
UserService userService = new UserService();
PermissionChecker checker = new PermissionChecker();
checker.executeWithCheck(userService, "deleteUser"); // 输出:执行删除用户操作(权限足够)
checker.executeWithCheck(userService, "queryUser"); // 输出:执行查询用户操作(权限足够)
}
}
解析流程:
- 定义注解:用
@Target和@Retention指定使用位置和生命周期。 - 使用注解:在目标方法上添加注解,设置属性值。
- 反射解析:通过
method.isAnnotationPresent()判断是否有注解,method.getAnnotation()获取注解对象,进而获取属性值并执行逻辑。
四、三者关系与总结
- 泛型:编译时保障类型安全,避免强制转换,是编写健壮代码的基础。
- 反射:运行时动态操作类信息,突破编译时限制,是框架灵活性的核心。
- 注解:通过标记携带元数据,配合反射实现 “标记 - 解析 - 执行” 的逻辑,是简化配置的关键。
三者结合构成了 Java 高级编程的基础 —— 泛型保证类型安全,反射提供动态能力,注解简化元数据携带,共同支撑了 Spring、MyBatis 等主流框架的设计。
到此这篇关于javaSE泛型、反射与注解的核心原理与实际应用的文章就介绍到这了,更多相关javaSE泛型、反射与注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
