> 技术文档 > 全栈学习 ——javaSE(五)泛型、反射与注解

全栈学习 ——javaSE(五)泛型、反射与注解

目录

一、泛型(Generic):编译时的类型安全保障

1.1 泛型的核心作用

1.2 泛型的基本用法

1.2.1 泛型类与泛型接口

1.2.2 泛型方法

1.2.3 类型通配符(Wildcard)

1.3 泛型的局限

二、反射(Reflection):运行时的类信息操作

2.1 反射的核心作用

2.2 反射的核心类

2.3 反射的基本操作

2.3.1 获取 Class 对象(反射入口)

2.3.2 反射创建对象(通过构造器)

2.3.3 反射调用方法(包括私有方法)

2.3.4 反射操作属性(包括私有属性)

2.4 反射的应用场景

2.5 反射的优缺点

三、注解(Annotation):代码的标记与元数据

3.1 注解的核心作用

3.2 常用内置注解

3.3 元注解:定义注解的注解

3.4 自定义注解及解析

四、三者关系与总结


泛型、反射与注解是 JavaSE 中支撑 “灵活编程” 与 “框架设计” 的核心技术。泛型解决 “类型安全” 问题,反射实现 “运行时动态操作类”,注解提供 “代码标记与元数据” 能力 —— 三者结合构成了 Java 框架(如 Spring、MyBatis)的底层基础。本章节将系统讲解这三项技术的核心原理与实际应用。

一、泛型(Generic):编译时的类型安全保障

在泛型出现之前,集合(如List)默认存储Object类型,取出元素时需强制转换,容易出现ClassCastException(类型转换异常)。泛型通过 “编译时类型指定”,让集合只能存储特定类型元素,从源头避免类型错误。

1.1 泛型的核心作用

  • 编译时类型检查:限制集合(或泛型类)只能存储指定类型元素,编译阶段就报错,而非运行时崩溃。
  • 避免强制转换:取出元素时无需手动转换(编译器自动确认类型)。
  • 代码复用:一套逻辑支持多种类型(如ListList共用List的实现)。

1.2 泛型的基本用法

1.2.1 泛型类与泛型接口

泛型类 / 接口在定义时声明 “类型参数”(如),使用时指定具体类型(如String)。

泛型类示例

// 定义泛型类:声明类型参数T(Type的缩写,可自定义名称)class GenericBox { // 使用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 stringBox = new GenericBox(); stringBox.setValue(\"Hello\"); // 正确:存入String // stringBox.setValue(123); // 编译错误:不能存Integer // 取出时无需转换(自动为String类型) String str = stringBox.getValue(); System.out.println(str); // 输出:Hello // 指定类型为Integer GenericBox intBox = new GenericBox(); intBox.setValue(100); Integer num = intBox.getValue(); // 无需转换 System.out.println(num); // 输出:100 }}

泛型接口示例

// 定义泛型接口(支持多种类型的“生产者”)interface Producer { T produce();}// 实现泛型接口时指定具体类型(如String)class StringProducer implements Producer { @Override public String produce() { return \"生产的字符串\"; }}// 实现时保留泛型(让子类也成为泛型类)class NumberProducer implements Producer { 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 strProducer = new StringProducer(); String str = strProducer.produce(); System.out.println(str); // 输出:生产的字符串 Producer intProducer = new NumberProducer(100); Integer num = intProducer.produce(); System.out.println(num); // 输出:100 }}
1.2.2 泛型方法

泛型方法在方法声明时独立声明类型参数(与类是否泛型无关),适用于 “单个方法需要支持多种类型” 的场景。

class GenericMethodDemo { // 定义泛型方法:是方法的类型参数,声明在返回值前 public  void printArray(E[] array) { for (E element : array) { System.out.print(element + \" \"); } System.out.println(); } // 泛型方法带返回值 public  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 }}

泛型方法特点

  • 类型参数声明在方法返回值前(如),与类的泛型参数无关。
  • 调用时编译器自动推断类型(无需手动指定,如传入String[]E自动为String)。
1.2.3 类型通配符(Wildcard)

当需要处理 “未知类型的泛型” 时(如方法参数需要接收任意泛型List),使用通配符?及限定符(extendssuper)控制类型范围。

通配符形式 含义 适用场景 任意类型(无限制) 仅读取元素,不修改(如打印任意 List) 上限:只能是 T 或 T 的子类 读取(可获取 T 类型),不能添加(除 null) 下限:只能是 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. 上限通配符:只能接收Number或其子类(如Integer、Double) public static double sum(List list) { double total = 0; for (Number num : list) { // 可安全转为Number total += num.doubleValue(); // 调用Number的方法 } return total; } // 3. 下限通配符:只能接收Integer或其父类(如Number、Object) public static void addIntegers(List list) { list.add(10); // 可添加Integer(或其子类,如Integer本身) list.add(20); // Integer num = list.get(0); // 编译错误:只能用Object接收 } public static void main(String[] args) { // 测试 List strList = List.of(\"A\", \"B\"); List intList = List.of(1, 2); printList(strList); // 输出:A B printList(intList); // 输出:1 2 // 测试 List integerList = List.of(1, 2, 3); List doubleList = List.of(1.5, 2.5); System.out.println(\"整数和:\" + sum(integerList)); // 输出:6.0 System.out.println(\"小数和:\" + sum(doubleList)); // 输出:4.0 // 测试 List numberList = new ArrayList(); addIntegers(numberList); // 向NumberList添加Integer System.out.println(\"添加后:\" + numberList); // 输出:[10, 20] }}

1.3 泛型的局限

  • 不能用基本类型:泛型参数只能是引用类型(如List错误,需用List)。
  • 运行时擦除:泛型信息在编译后被擦除(如ListList运行时都是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\"); // 输出:执行查询用户操作(权限足够) }}

解析流程

  1. 定义注解:用@Target@Retention指定使用位置和生命周期。
  2. 使用注解:在目标方法上添加注解,设置属性值。
  3. 反射解析:通过method.isAnnotationPresent()判断是否有注解,method.getAnnotation()获取注解对象,进而获取属性值并执行逻辑。

四、三者关系与总结

  • 泛型:编译时保障类型安全,避免强制转换,是编写健壮代码的基础。
  • 反射:运行时动态操作类信息,突破编译时限制,是框架灵活性的核心。
  • 注解:通过标记携带元数据,配合反射实现 “标记 - 解析 - 执行” 的逻辑,是简化配置的关键。

三者结合构成了 Java 高级编程的基础 —— 泛型保证类型安全,反射提供动态能力,注解简化元数据携带,共同支撑了 Spring、MyBatis 等主流框架的设计。