第四章:泛型
4.1 什么是泛型?为什么需要泛型?
4.1.1 泛型的基本概念和作用
定义:泛型是Java 5引入的一种参数化类型机制,允许在定义类、接口、方法时使用类型参数。
核心概念:
- 类型参数:用尖括号<>表示,如
<T> - 类型实参:使用时传入的具体类型,如
String - 泛型类型:带有类型参数的类或接口
基本示例:
java
// 不使用泛型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制类型转换
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动类型推断,无需强制转换
4.1.2 泛型带来的好处
类型安全:
- 编译时类型检查,避免运行时
ClassCastException - 错误在编译期就能发现,而不是运行时
代码复用:
- 编写一次,多种类型使用
- 减少代码重复,提高可维护性
代码简洁:
- 消除强制类型转换
- 代码更易读、更清晰
对比示例:
| 方面 | 无泛型 | 有泛型 |
|---|---|---|
| 类型安全 | 运行时可能抛出ClassCastException | 编译时检查,类型安全 |
| 类型转换 | 需要显式强制类型转换 | 自动类型推断,无需转换 |
| 代码简洁性 | 代码冗长,可读性差 | 代码简洁,意图明确 |
| 错误检测 | 运行时才能发现类型错误 | 编译时就能发现错误 |
java
// 不使用泛型 - 可能出错
List list = new ArrayList();
list.add("hello");
list.add(123); // 可以添加不同类型
String s = (String) list.get(1); // 运行时抛出ClassCastException
// 使用泛型 - 类型安全
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误:无法添加Integer
String s = list.get(0); // 安全,自动类型推断
4.1.3 泛型的历史背景
发展历程:
| 时间 | 版本 | 特性 | 问题 |
|---|---|---|---|
| Java 1.0-1.4 | 无泛型 | 使用Object和强制类型转换 | 类型不安全,代码冗长 |
| 2004年 | Java 5 | 引入泛型 | 编译时类型检查 |
| 设计目标 | - | 向后兼容,类型安全 | 引入类型擦除实现兼容 |
设计目标:
- 向后兼容:老代码无需修改就能运行
- 类型安全:编译时检查类型错误
- 代码复用:一套代码多种类型使用
4.1.4 泛型在集合框架中的应用
集合框架的泛型化:
| 集合类 | 无泛型版本 | 有泛型版本 | 主要改进 |
|---|---|---|---|
| List | List | List<E> | 元素类型安全 |
| Set | Set | Set<E> | 元素类型安全 |
| Map | Map | Map<K,V> | 键值类型安全 |
| Iterator | Iterator | Iterator<E> | 迭代类型安全 |
集合框架示例对比:
java
// Java 5之前:不使用泛型
Map map = new HashMap();
map.put("key", "value");
map.put(123, "number"); // 可以混合类型
String value = (String) map.get("key"); // 需要强制转换
// String wrong = (String) map.get(123); // 运行时错误
// Java 5之后:使用泛型
Map<String, String> map = new HashMap<>();
map.put("key", "value");
// map.put(123, "number"); // 编译错误:类型不匹配
String value = map.get("key"); // 自动类型推断,无需转换
总结:
- 泛型是Java的类型参数化机制
- 主要优点:类型安全、代码复用、消除强制转换
- 集合框架是泛型最主要的应用场景
- 泛型让代码更安全、更简洁、更易维护
4.2 泛型擦除是什么?为什么要擦除?
4.2.1 泛型擦除的概念和表现
定义:泛型擦除是Java泛型的实现机制,编译器在编译期间移除所有泛型类型信息,生成的字节码中不包含泛型。
表现:
- 运行时无法获取泛型的具体类型
- 泛型类型参数被替换为原始类型(Object或边界类型)
- 编译时检查,运行时擦除
示例:
java
// 源代码
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);
// 编译后(概念上的等效代码)
List list = new ArrayList(); // 泛型被擦除
list.add("hello");
String s = (String) list.get(0); // 插入强制类型转换
擦除验证:
java
// 验证泛型擦除
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// 检查运行时类型
System.out.println(stringList.getClass()); // class java.util.ArrayList
System.out.println(integerList.getClass()); // class java.util.ArrayList
// 两者类型相同,说明泛型信息被擦除
// 以下代码在运行时是合法的(通过反射绕过编译检查)
List list = stringList;
list.add(123); // 编译时会有警告,但运行时能添加Integer
System.out.println(stringList.get(1)); // 运行时抛出ClassCastException
4.2.2 泛型擦除的底层实现原理
擦除过程:
| 泛型类型 | 擦除后类型 | 说明 |
|---|---|---|
List<T> | List | 无界类型参数擦除为Object |
List<? extends Number> | List | 上界通配符擦除为上界类型 |
List<? super Integer> | List | 下界通配符擦除为Object |
List<String> | List | 具体类型擦除为原始类型 |
擦除示例:
java
// 泛型类定义
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 擦除后的等效代码(概念上)
public class Box {
private Object value; // T被擦除为Object
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
4.2.3 为什么需要泛型擦除(向后兼容)
核心原因:保持向后兼容性
Java版本兼容性对比:
| 版本 | 特性 | 兼容性问题 |
|---|---|---|
| Java 1.0-1.4 | 无泛型 | - |
| Java 5 | 引入泛型 | 需要兼容老版本代码 |
兼容性解决方案:
- 源代码兼容:老代码无需修改能编译
- 二进制兼容:老class文件能在新JVM运行
- 迁移兼容:混合使用泛型和非泛型代码
示例:
java
// Java 1.4代码(无泛型)
List list = new ArrayList();
list.add("old code");
// Java 5+代码(有泛型)
List<String> newList = new ArrayList<>();
// 两者可以混合使用(为了兼容)
list = newList; // 允许,但有警告
newList = list; // 允许,但有警告
4.2.4 擦除带来的限制
主要限制:
| 限制 | 描述 | 示例 |
|---|---|---|
| 无法实例化 | 不能new T() | T obj = new T(); ❌ |
| 无法创建数组 | 不能new T[] | T[] array = new T[10]; ❌ |
| 无法使用instanceof | 不能检查泛型类型 | obj instanceof List<String> ❌ |
| 静态成员共享 | 静态变量被所有实例共享 | static T value; ❌ |
| 无法重载 | 擦除后方法签名相同 | void method(List<String>)和void method(List<Integer>) ❌ |
限制示例:
java
public class Limitations<T> {
// 1. 不能实例化类型参数
// T obj = new T(); // 编译错误
// 2. 不能创建泛型数组
// T[] array = new T[10]; // 编译错误
// 3. 静态上下文中不能使用类型参数
// static T value; // 编译错误
// 4. instanceof不能用于泛型类型
public boolean isStringList(Object obj) {
// return obj instanceof List<String>; // 编译错误
return obj instanceof List; // 只能这样
}
// 5. 类型参数不能用于异常
// class MyException<T> extends Exception {} // 编译错误
}
4.2.5 桥方法(Bridge Method)机制
概念:编译器生成的方法,用于在泛型擦除后保持多态性。
产生场景:泛型类或接口被继承/实现时
示例:
java
// 泛型接口
interface Comparable<T> {
int compareTo(T other);
}
// 实现类
class StringComparable implements Comparable<String> {
@Override
public int compareTo(String other) {
return 0; // 实现
}
}
// 编译后生成的桥方法
class StringComparable implements Comparable<String> {
// 实际的方法
public int compareTo(String other) {
return 0;
}
// 编译器生成的桥方法(为了兼容擦除)
public int compareTo(Object other) {
return compareTo((String) other); // 调用实际的方法
}
}
桥方法的作用:
- 保持Java泛型的多态性
- 确保子类正确重写父类方法
- 处理类型擦除带来的方法签名变化
查看桥方法:
java
Method[] methods = StringComparable.class.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + ": " + method.isBridge());
}
// 输出包含桥方法的信息
总结:
- 泛型擦除是Java泛型的实现机制,为了向后兼容
- 优点:兼容老代码,运行时性能几乎无影响
- 缺点:带来诸多限制,运行时无法获取类型信息
- 桥方法:编译器自动生成,保证多态性
- 设计权衡:在类型安全和兼容性之间取得平衡
4.3 Java泛型中类型擦除的理解及局限
4.3.1 泛型的8个使用限制详解
完整限制列表:
| 序号 | 限制 | 描述 | 代码示例 |
|---|---|---|---|
| 1 | 不能实例化类型参数 | 无法new T() | T obj = new T(); ❌ |
| 2 | 不能创建泛型数组 | 无法new T[] | T[] arr = new T[10]; ❌ |
| 3 | 静态成员不能使用类型参数 | 静态域或方法中不能用T | static T value; ❌ |
| 4 | instanceof不能用于泛型类型 | 无法检查List<String> | obj instanceof List<String> ❌ |
| 5 | 基本类型不能作为类型参数 | 必须使用包装类 | List<int> ❌,List<Integer> ✅ |
| 6 | 不能创建异常泛型类 | 异常类不能有类型参数 | class MyEx<T> extends Exception ❌ |
| 7 | 不能重载擦除后相同的方法 | 方法签名擦除后相同 | void m(List<String>)和void m(List<Integer>) ❌ |
| 8 | 不能捕获泛型异常 | catch不能捕获类型参数 | catch (T e) ❌ |
4.3.2 不能使用基本类型的原因
原因分析:
- 泛型擦除机制:类型参数擦除为Object或其边界类型
- Object不能存储基本类型:需要自动装箱/拆箱
- 性能考虑:避免自动装箱带来的性能开销
对比示例:
java
// 错误:不能使用基本类型
// List<int> intList = new ArrayList<>(); // 编译错误
// 正确:使用包装类
List<Integer> integerList = new ArrayList<>();
// 自动装箱和拆箱
integerList.add(123); // 自动装箱 int → Integer
int value = integerList.get(0); // 自动拆箱 Integer → int
// 性能对比
long start = System.nanoTime();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add(i); // 自动装箱,有性能开销
}
long end = System.nanoTime();
System.out.println("泛型列表耗时: " + (end - start) + " ns");
// 使用基本类型数组性能更好
start = System.nanoTime();
int[] array = new int[1000000];
for (int i = 0; i < 1000000; i++) {
array[i] = i; // 直接赋值,无装箱开销
}
end = System.nanoTime();
System.out.println("基本数组耗时: " + (end - start) + " ns");
4.3.3 泛型数组的限制
限制原因:
- 类型安全:数组是协变的,运行时检查元素类型
- 擦除冲突:泛型擦除后类型信息丢失,无法进行数组类型检查
错误示例:
java
// 1. 不能直接创建泛型数组
// List<String>[] array = new List<String>[10]; // 编译错误
// 2. 这样也不可以
// T[] array = new T[10]; // 编译错误
// 3. 为什么不行?类型安全问题
// 假设允许:
// List<String>[] stringLists = new List<String>[1]; // 假设允许
// List<Integer> intList = Arrays.asList(42);
// Object[] objects = stringLists; // 数组是协变的,允许向上转型
// objects[0] = intList; // 可以赋值,因为Object[]可以存储任何Object
// String s = stringLists[0].get(0); // 运行时错误:ClassCastException
解决方案:
java
// 方案1:使用Object[]然后强制转换(不安全)
List<String>[] array = (List<String>[]) new List[10]; // 警告,但不报错
array[0] = new ArrayList<String>();
// 方案2:使用ArrayList替代数组(推荐)
List<List<String>> listOfLists = new ArrayList<>();
listOfLists.add(new ArrayList<>());
// 方案3:使用反射创建数组(复杂,类型不安全)
public static <T> T[] createArray(Class<T> type, int size) {
return (T[]) Array.newInstance(type, size);
}
String[] stringArray = createArray(String.class, 10);
4.3.4 instanceof不能用于泛型类型
限制原因:
- 类型擦除:运行时无法获取泛型类型信息
- instanceof需要运行时类型信息
错误示例:
java
List<String> list = new ArrayList<>();
// 错误:不能检查具体泛型类型
// if (list instanceof List<String>) { // 编译错误
// System.out.println("是字符串列表");
// }
// 只能检查原始类型
if (list instanceof List) { // 正确,但不够精确
System.out.println("是List,但不知道元素类型");
}
// 解决方案:检查元素类型
if (!list.isEmpty() && list.get(0) instanceof String) {
System.out.println("列表元素是String类型");
}
4.3.5 不能创建泛型实例
限制原因:类型擦除导致运行时不知道T的具体类型
错误示例:
java
class Container<T> {
// 错误:不能直接实例化类型参数
// public T createInstance() {
// return new T(); // 编译错误
// }
// 错误:也不能创建数组
// public T[] createArray() {
// return new T[10]; // 编译错误
// }
}
解决方案:
java
// 方案1:传入Class对象
class Container<T> {
private Class<T> type;
public Container(Class<T> type) {
this.type = type;
}
public T createInstance() throws Exception {
return type.newInstance(); // 使用反射创建实例
}
public T[] createArray(int size) {
return (T[]) Array.newInstance(type, size);
}
}
Container<String> container = new Container<>(String.class);
String str = container.createInstance(); // 创建String实例
String[] array = container.createArray(10); // 创建String数组
4.3.6 静态上下文中不能使用类型参数
限制原因:
- 静态成员属于类,而不是实例
- 类型参数属于实例
- 不同实例可能有不同的类型实参
错误示例:
java
class Box<T> {
// 错误:静态变量不能使用类型参数
// private static T value; // 编译错误
// 错误:静态方法不能使用类的类型参数
// public static T getValue() { // 编译错误
// return value;
// }
// 正确:静态方法可以有自己独立的类型参数
public static <U> U staticMethod(U param) {
return param;
}
}
正确用法:
java
class Utils {
// 泛型静态方法
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// 多个类型参数的静态方法
public static <K, V> Map<K, V> createMap(K key, V value) {
Map<K, V> map = new HashMap<>();
map.put(key, value);
return map;
}
}
// 使用
String first = Utils.getFirst(Arrays.asList("a", "b", "c"));
Map<Integer, String> map = Utils.createMap(1, "one");
总结表格:
| 限制 | 原因 | 解决方案 |
|---|---|---|
| 不能实例化T | 运行时类型信息擦除 | 传入Class对象,使用反射 |
| 不能创建泛型数组 | 数组协变和类型安全 | 使用集合或反射创建数组 |
| instanceof限制 | 运行时无泛型信息 | 检查元素类型而非容器类型 |
| 不能使用基本类型 | Object不能存储基本类型 | 使用包装类,注意性能 |
| 静态成员限制 | 类型参数属于实例 | 静态方法用独立类型参数 |
核心理解:
- 泛型是编译时概念,运行时信息被擦除
- 限制源于类型擦除和Java的类型系统设计
- 理解限制有助于避免常见错误,写出正确的泛型代码
4.4 什么是通配符?上界通配符和下界通配符的区别
4.4.1 无界通配符(?)
定义:?表示未知类型,可以匹配任何类型。
使用场景:
- 只关心容器操作,不关心元素类型
- 使用Object类中的方法操作元素
- 读取时返回Object,写入时受限
示例:
java
// 无界通配符参数
public void processList(List<?> list) {
// 可以读取,返回Object
Object obj = list.get(0);
// 不能添加元素(除了null)
// list.add("string"); // 编译错误,类型未知
list.add(null); // 允许,null可以赋值给任何类型
// 可以调用与类型无关的方法
System.out.println("Size: " + list.size());
list.clear();
}
// 使用
List<String> stringList = Arrays.asList("a", "b");
List<Integer> intList = Arrays.asList(1, 2, 3);
processList(stringList); // 可以处理任何类型的List
processList(intList);
4.4.2 上界通配符(? extends T)
定义:? extends T表示T或T的子类。
特点:
- 安全读取:可以安全地读取为T类型
- 限制写入:不能添加任何元素(除了null)
示例:
java
// 上界通配符
public double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) { // 安全读取为Number
total += n.doubleValue();
}
// 不能添加元素
// numbers.add(new Integer(10)); // 编译错误
// numbers.add(new Double(3.14)); // 编译错误
numbers.add(null); // 允许
return total;
}
// 使用
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println(sum(integers)); // 6.0
System.out.println(sum(doubles)); // 6.6
// 也可以传入Number列表
List<Number> numbers = Arrays.asList(1, 2.5, 3);
System.out.println(sum(numbers)); // 6.5
4.4.3 下界通配符(? super T)
定义:? super T表示T或T的父类。
特点:
- 安全写入:可以添加T类型及其子类的元素
- 限制读取:读取时只能得到Object类型
示例:
java
// 下界通配符
public void addNumbers(List<? super Integer> list) {
// 可以安全地添加Integer及其子类
list.add(10);
list.add(20);
// 也可以添加Integer的子类(如果有)
// list.add(30); // 自动装箱为Integer
// 读取时只能得到Object
Object obj = list.get(0);
// 需要强制类型转换才能得到具体类型
if (obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.println("Got integer: " + num);
}
}
// 使用
List<Number> numbers = new ArrayList<>();
addNumbers(numbers); // 可以添加Integer到Number列表
System.out.println(numbers); // [10, 20]
List<Object> objects = new ArrayList<>();
addNumbers(objects); // 可以添加Integer到Object列表
System.out.println(objects); // [10, 20]
// List<String> strings = new ArrayList<>();
// addNumbers(strings); // 编译错误:String不是Integer的父类
4.4.4 PECS原则(Producer Extends, Consumer Super)
PECS原则:
- Producer Extends:生产者(提供数据)使用
extends - Consumer Super:消费者(接收数据)使用
super
记忆口诀:
- 需要从集合取数据(生产数据):用
extends - 需要向集合存数据(消费数据):用
super - 既取又存:不要用通配符,用具体类型
PECS应用示例:
java
// 1. 生产者 - 只读
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list) { // 生产Number对象
s += n.doubleValue();
}
return s;
}
// 2. 消费者 - 只写
public static void addIntegers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i); // 消费Integer对象
}
}
// 3. 既生产又消费 - 不用通配符
public static <T> void copy(List<T> dest, List<T> src) {
for (T element : src) { // 生产T
dest.add(element); // 消费T
}
}
// 4. 更灵活的拷贝(PECS版)
public static <T> void copyPECS(List<? super T> dest, List<? extends T> src) {
for (T element : src) { // src生产T
dest.add(element); // dest消费T
}
}
// 使用PECS拷贝
List<Number> numbers = new ArrayList<>();
List<Integer> integers = Arrays.asList(1, 2, 3);
copyPECS(numbers, integers); // 可以复制Integer到Number列表
System.out.println(numbers); // [1, 2, 3]
4.4.5 通配符捕获
概念:通配符?表示未知类型,但有时需要引用这个未知类型。
问题:不能直接使用?作为类型变量
错误示例:
java
// 错误:不能直接使用?
public static void swap(List<?> list, int i, int j) {
// 不能直接声明?类型的变量
// ? temp = list.get(i); // 编译错误
// 需要通配符捕获
swapHelper(list, i, j);
}
解决方案:通配符捕获辅助方法
java
// 辅助方法捕获通配符
private static <E> void swapHelper(List<E> list, int i, int j) {
E temp = list.get(i); // 这里可以声明E类型的变量
list.set(i, list.get(j));
list.set(j, temp);
}
// 使用
List<String> list = Arrays.asList("a", "b", "c");
swapHelper(list, 0, 2); // 交换位置0和2
System.out.println(list); // [c, b, a]
4.4.6 通配符的使用场景
场景对比总结:
| 场景 | 推荐通配符 | 示例 | 说明 |
|---|---|---|---|
| 只读不写 | ? extends T | void print(List<? extends Number>) | 安全读取,限制写入 |
| 只写不读 | ? super T | void fill(List<? super Integer>) | 安全写入,限制读取 |
| 读写都需要 | 不用通配符 | <T> void copy(List<T> src, List<T> dest) | 类型参数提供灵活性 |
| 完全类型无关 | ? | int size(Collection<?> coll) | 只关心集合操作 |
| PECS模式 | 组合使用 | <T> void copy(List<? super T> dest, List<? extends T> src) | 生产消费分离 |
实际应用示例:
java
// Java Collections API中的通配符使用
// 1. Collections.copy() - PECS原则
// public static <T> void copy(List<? super T> dest, List<? extends T> src)
// 2. Collections.addAll() - 下界通配符
// public static <T> boolean addAll(Collection<? super T> c, T... elements)
// 3. Collections.max() - 上界通配符
// public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
// 自定义示例
class DataProcessor {
// 场景1:处理任何类型的集合
public static void processAny(Collection<?> collection) {
System.out.println("Processing " + collection.size() + " elements");
}
// 场景2:统计数值型集合的总和
public static double sumCollection(Collection<? extends Number> numbers) {
return numbers.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
// 场景3:向集合添加默认值
public static <T> void addDefaults(Collection<? super T> collection, T defaultValue, int count) {
for (int i = 0; i < count; i++) {
collection.add(defaultValue);
}
}
}
总结:
- 无界通配符
?:类型完全未知,限制写入 - 上界通配符
? extends T:生产者,只读不写 - 下界通配符
? super T:消费者,只写不读 - PECS原则:指导何时使用哪种通配符
- 通配符捕获:通过辅助方法处理未知类型
- 使用场景:根据读写需求选择合适的通配符
4.5 泛型中K、T、V、E、?等符号的含义
4.5.1 类型参数的命名规范
Java泛型命名约定:
| 符号 | 含义 | 常用场景 |
|---|---|---|
| T | Type(类型) | 通用类型参数 |
| E | Element(元素) | 集合中的元素类型 |
| K | Key(键) | Map的键类型 |
| V | Value(值) | Map的值类型 |
| N | Number(数字) | 数值类型 |
| ? | 通配符 | 未知类型 |
| R | Return(返回值) | 方法返回值类型 |
| U, S | 第二、第三类型 | 多个类型参数 |
命名规则:
- 单大写字母,简洁明了
- 有意义的字母表示用途
- 多个类型参数按字母顺序:T, U, V, S等
- 通配符
?单独使用,表示未知类型
4.5.2 常见泛型符号的含义
T (Type) - 最常用的类型参数
java
// 通用容器类
class Container<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用
Container<String> stringContainer = new Container<>();
stringContainer.set("Hello");
String s = stringContainer.get();
E (Element) - 集合元素类型
java
// 自定义集合接口
interface Stack<E> {
void push(E element);
E pop();
boolean isEmpty();
}
// 实现
class ArrayStack<E> implements Stack<E> {
private List<E> elements = new ArrayList<>();
@Override
public void push(E element) {
elements.add(element);
}
@Override
public E pop() {
return elements.remove(elements.size() - 1);
}
// 其他方法...
}
K, V (Key, Value) - Map的键值类型
java
// 自定义Map条目
class Entry<K, V> {
private K key;
private V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 使用
Entry<String, Integer> entry = new Entry<>("age", 25);
String key = entry.getKey(); // String
Integer value = entry.getValue(); // Integer
N (Number) - 数值类型参数
java
// 数值处理器
class NumberProcessor<N extends Number> {
private N number;
public NumberProcessor(N number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue();
}
}
// 使用
NumberProcessor<Integer> intProcessor = new NumberProcessor<>(100);
NumberProcessor<Double> doubleProcessor = new NumberProcessor<>(3.14);
R (Return) - 返回值类型
java
// 函数式接口中的返回值类型
@FunctionalInterface
interface Function<T, R> {
R apply(T input);
}
// 使用
Function<String, Integer> stringToLength = String::length;
int length = stringToLength.apply("hello"); // 5
4.5.3 多个类型参数的使用
多个类型参数示例:
java
// 两个类型参数
class Pair<T, U> {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() { return first; }
public U getSecond() { return second; }
}
// 三个类型参数
class Triple<T, U, V> {
private T first;
private U second;
private V third;
// 构造函数、getter、setter...
}
// 使用
Pair<String, Integer> nameAndAge = new Pair<>("Alice", 30);
Triple<String, Integer, Boolean> triple = new Triple<>("Bob", 25, true);
复杂示例:Map模拟
java
class SimpleMap<K, V> {
private List<Pair<K, V>> entries = new ArrayList<>();
public void put(K key, V value) {
// 简单实现,不考虑key重复
entries.add(new Pair<>(key, value));
}
public V get(K key) {
for (Pair<K, V> entry : entries) {
if (entry.getFirst().equals(key)) {
return entry.getSecond();
}
}
return null;
}
}
// 使用
SimpleMap<String, Integer> scores = new SimpleMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
System.out.println(scores.get("Alice")); // 95
4.5.4 泛型方法和泛型类
泛型类 vs 泛型方法对比:
| 方面 | 泛型类 | 泛型方法 |
|---|---|---|
| 定义位置 | 类/接口声明处 | 方法声明处 |
| 作用范围 | 整个类/接口 | 单个方法 |
| 类型参数 | 类级别,实例共享 | 方法级别,独立 |
| 静态方法 | 不能使用类的类型参数 | 可以有独立的类型参数 |
| 使用场景 | 容器类、数据结构 | 工具方法、算法 |
泛型类示例:
java
// 泛型类
class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
// 注意:静态方法不能使用T
public static <U> Box<U> createBox(U content) {
return new Box<>(content);
}
}
泛型方法示例:
java
// 工具类中的泛型方法
class ArrayUtils {
// 泛型方法:交换数组元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 泛型方法:查找最大值
public static <T extends Comparable<T>> T max(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
// 多个类型参数的泛型方法
public static <K, V> String formatPair(K key, V value) {
return key + " -> " + value;
}
}
// 使用
String[] names = {"Alice", "Bob", "Charlie"};
ArrayUtils.swap(names, 0, 2); // 交换第一个和最后一个
Integer[] numbers = {3, 1, 4, 1, 5, 9};
Integer maxNumber = ArrayUtils.max(numbers); // 9
String formatted = ArrayUtils.formatPair("age", 25);
4.5.5 泛型接口
泛型接口定义:
java
// 泛型接口
interface Repository<T> {
void save(T entity);
T findById(String id);
List<T> findAll();
void delete(T entity);
}
// 实现泛型接口
class UserRepository implements Repository<User> {
private List<User> users = new ArrayList<>();
@Override
public void save(User user) {
users.add(user);
}
@Override
public User findById(String id) {
return users.stream()
.filter(user -> user.getId().equals(id))
.findFirst()
.orElse(null);
}
@Override
public List<User> findAll() {
return new ArrayList<>(users);
}
@Override
public void delete(User user) {
users.remove(user);
}
}
// 用户类
class User {
private String id;
private String name;
// 构造器、getter、setter...
}
泛型接口的多种实现方式:
java
// 方式1:实现时指定具体类型
class StringRepository implements Repository<String> {
// 实现方法...
}
// 方式2:保持泛型,实现类也是泛型类
class GenericRepository<T> implements Repository<T> {
private List<T> items = new ArrayList<>();
@Override
public void save(T item) {
items.add(item);
}
// 其他方法实现...
}
// 方式3:实现有界泛型接口
interface ComparableRepository<T extends Comparable<T>> extends Repository<T> {
List<T> findSorted();
}
class ComparableUserRepository implements ComparableRepository<User> {
// 需要实现Repository的所有方法
// 以及ComparableRepository的findSorted方法
}
总结表格:
| 符号 | 含义 | 典型用法 | 示例 |
|---|---|---|---|
| T | 通用类型 | 类、方法、接口的类型参数 | class Box<T> |
| E | 元素类型 | 集合框架 | List<E>, Set<E> |
| K | 键类型 | Map的键 | Map<K, V> |
| V | 值类型 | Map的值 | Map<K, V> |
| N | 数值类型 | 数值处理 | N extends Number |
| R | 返回值 | 函数式接口 | Function<T, R> |
| ? | 通配符 | 未知类型 | List<?> |
| U, S | 第二、三类型 | 多个类型参数 | Pair<T, U> |
最佳实践:
- 遵循命名约定,提高代码可读性
- 根据需要选择泛型类或泛型方法
- 泛型接口提供灵活的抽象
- 合理使用有界类型参数增加类型安全
- 泛型方法特别适合工具类实现
4.6 泛型高级特性
4.6.1 泛型与反射
反射获取泛型信息:
- 运行时类型擦除导致无法直接获取泛型类型
- 通过反射的
ParameterizedType可以获取部分泛型信息
获取泛型信息的三种方式:
java
import java.lang.reflect.*;
import java.util.*;
// 1. 获取类的泛型信息
class GenericClass<T> {
private T value;
}
// 2. 获取字段的泛型信息
class TypeExample {
private List<String> stringList;
private Map<String, Integer> map;
}
// 3. 获取方法的泛型信息
class MethodExample {
public List<String> getStringList() {
return new ArrayList<>();
}
}
public class GenericReflection {
public static void main(String[] args) throws Exception {
// 1. 获取类的泛型参数
Type type = GenericClass.class.getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] actualTypes = pt.getActualTypeArguments();
System.out.println("类泛型参数: " + Arrays.toString(actualTypes));
}
// 2. 获取字段的泛型类型
Field field = TypeExample.class.getDeclaredField("stringList");
Type fieldType = field.getGenericType();
if (fieldType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) fieldType;
System.out.println("字段类型: " + pt.getRawType());
System.out.println("泛型参数: " + Arrays.toString(pt.getActualTypeArguments()));
}
// 3. 获取方法的返回类型
Method method = MethodExample.class.getMethod("getStringList");
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) returnType;
System.out.println("方法返回类型: " + pt.getRawType());
System.out.println("泛型参数: " + Arrays.toString(pt.getActualTypeArguments()));
}
// 4. 获取匿名内部类的泛型信息
List<String> list = new ArrayList<String>() {};
Type listType = list.getClass().getGenericSuperclass();
if (listType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) listType;
System.out.println("匿名类泛型: " + Arrays.toString(pt.getActualTypeArguments()));
}
}
}
泛型数组的反射创建:
java
public class GenericArrayReflection {
// 创建泛型数组的通用方法
@SuppressWarnings("unchecked")
public static <T> T[] createGenericArray(Class<T> type, int length) {
return (T[]) Array.newInstance(type, length);
}
// 获取数组元素的类型
public static Class<?> getComponentType(Object array) {
return array.getClass().getComponentType();
}
public static void main(String[] args) {
// 创建泛型数组
String[] stringArray = createGenericArray(String.class, 10);
Integer[] intArray = createGenericArray(Integer.class, 5);
// 填充数组
Arrays.fill(stringArray, "hello");
for (int i = 0; i < intArray.length; i++) {
intArray[i] = i * i;
}
System.out.println("字符串数组: " + Arrays.toString(stringArray));
System.out.println("整数数组: " + Arrays.toString(intArray));
// 获取数组元素类型
System.out.println("字符串数组元素类型: " + getComponentType(stringArray));
System.out.println("整数数组元素类型: " + getComponentType(intArray));
}
}
4.6.2 泛型与注解
泛型注解的定义和使用:
java
import java.lang.annotation.*;
import java.lang.reflect.*;
// 1. 泛型注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GenericAnnotation<T> {
// 注解属性不能使用泛型类型参数
// T value(); // 错误:注解属性不能是泛型
Class<T> type(); // 但可以使用Class<T>
String description() default "";
}
// 2. 使用泛型注解
@GenericAnnotation(type = String.class, description = "字符串处理器")
class StringProcessor {
public String process(String input) {
return input.toUpperCase();
}
}
@GenericAnnotation(type = Integer.class)
class IntegerProcessor {
public Integer process(Integer input) {
return input * 2;
}
}
// 3. 处理泛型注解
public class GenericAnnotationProcessor {
public static void main(String[] args) {
processAnnotation(StringProcessor.class);
processAnnotation(IntegerProcessor.class);
}
public static void processAnnotation(Class<?> clazz) {
GenericAnnotation annotation = clazz.getAnnotation(GenericAnnotation.class);
if (annotation != null) {
System.out.println("类: " + clazz.getSimpleName());
System.out.println("注解类型: " + annotation.type());
System.out.println("描述: " + annotation.description());
System.out.println();
}
}
}
// 4. 注解与泛型方法的结合
class Validator {
@SuppressWarnings("unchecked")
public static <T> T validateAndCast(Object obj, Class<T> expectedType) {
if (obj == null) {
throw new IllegalArgumentException("对象不能为null");
}
if (!expectedType.isInstance(obj)) {
throw new ClassCastException(
String.format("对象类型不匹配: 期望%s, 实际%s",
expectedType.getName(), obj.getClass().getName()));
}
return (T) obj;
}
// 使用注解增强泛型方法
@Deprecated
public static <T> T oldValidate(Object obj, Class<T> type) {
// 旧版本的实现
return validateAndCast(obj, type);
}
}
4.6.3 泛型与继承
泛型继承的几种形式:
java
// 1. 泛型类继承泛型类
class BaseClass<T> {
protected T value;
public BaseClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 子类保持泛型
class SubClass<T> extends BaseClass<T> {
public SubClass(T value) {
super(value);
}
// 可以添加额外的方法
public void process() {
System.out.println("处理: " + value);
}
}
// 子类指定具体类型
class StringClass extends BaseClass<String> {
public StringClass(String value) {
super(value);
}
// 可以添加字符串特有的方法
public int getLength() {
return value.length();
}
}
// 2. 泛型接口的实现
interface Processor<T> {
T process(T input);
}
// 实现时指定具体类型
class StringProcessorImpl implements Processor<String> {
@Override
public String process(String input) {
return input.toUpperCase();
}
}
// 实现时保持泛型
class GenericProcessor<T> implements Processor<T> {
@Override
public T process(T input) {
// 通用处理逻辑
return input;
}
}
// 3. 复杂的继承关系
interface Reader<T> {
T read();
}
interface Writer<T> {
void write(T data);
}
// 实现多个泛型接口
class FileHandler<T> implements Reader<T>, Writer<T> {
@Override
public T read() {
// 实现读取逻辑
return null;
}
@Override
public void write(T data) {
// 实现写入逻辑
}
}
// 4. 泛型方法重写
class Base {
public <T> T genericMethod(T input) {
System.out.println("Base: " + input);
return input;
}
}
class Derived extends Base {
// 重写泛型方法
@Override
public <T> T genericMethod(T input) {
System.out.println("Derived: " + input);
return super.genericMethod(input);
}
}
// 5. 测试泛型继承
public class GenericInheritanceTest {
public static void main(String[] args) {
// 测试泛型类继承
SubClass<String> sub = new SubClass<>("hello");
System.out.println(sub.getValue()); // hello
sub.process(); // 处理: hello
StringClass strClass = new StringClass("world");
System.out.println(strClass.getValue()); // world
System.out.println(strClass.getLength()); // 5
// 测试泛型接口实现
Processor<String> processor = new StringProcessorImpl();
String result = processor.process("hello");
System.out.println(result); // HELLO
}
}
4.6.4 泛型的协变和逆变
协变和逆变概念:
| 概念 | 定义 | Java示例 | 说明 |
|---|---|---|---|
| 协变 | 子类型关系保持 | String[]是Object[]的子类型 | 数组是协变的 |
| 逆变 | 子类型关系反转 | Comparator<Object>是Comparator<String>的子类型? | 需要通配符 |
| 不变 | 无子类型关系 | List<String>不是List<Object>的子类型 | 泛型默认不变 |
数组协变的问题:
java
// 数组是协变的 - 可能引发问题
Object[] objectArray = new String[10]; // 允许:String[]是Object[]的子类型
objectArray[0] = "hello"; // 正确
// objectArray[1] = 123; // 运行时抛出ArrayStoreException
// 泛型是不变的 - 更安全
// List<Object> objectList = new ArrayList<String>(); // 编译错误
// List<String>不是List<Object>的子类型
使用通配符实现协变和逆变:
java
import java.util.*;
// 1. 协变:使用上界通配符(? extends)
public class CovariantExample {
// 协变读取
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("整数和: " + sum(integers)); // 6.0
System.out.println("双精度和: " + sum(doubles)); // 6.6
// 协变赋值
List<? extends Number> numbers;
numbers = integers; // 协变:List<Integer>可以赋值给List<? extends Number>
numbers = doubles; // 协变:List<Double>也可以
}
}
// 2. 逆变:使用下界通配符(? super)
public class ContravariantExample {
// 逆变写入
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 3; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(numbers); // 逆变:可以添加Integer到Number列表
addNumbers(objects); // 逆变:可以添加Integer到Object列表
System.out.println("numbers: " + numbers); // [1, 2, 3]
System.out.println("objects: " + objects); // [1, 2, 3]
// 逆变赋值
List<? super Integer> intSuper;
intSuper = numbers; // 逆变:List<Number>可以赋值给List<? super Integer>
intSuper = objects; // 逆变:List<Object>也可以
}
}
// 3. PECS原则的实际应用
public class PecsExample {
// 生产者使用extends(协变)
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
T candidate = null;
for (T element : coll) {
if (candidate == null || comp.compare(element, candidate) > 0) {
candidate = element;
}
}
return candidate;
}
// 消费者使用super(逆变)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T element : src) {
dest.add(element);
}
}
public static void main(String[] args) {
// 协变示例
List<Integer> integers = Arrays.asList(3, 1, 4, 1, 5);
// Comparator<Number>可以比较Integer(逆变)
Comparator<Number> numberComparator = Comparator.comparing(Number::intValue);
Integer maxInt = max(integers, numberComparator);
System.out.println("最大值: " + maxInt); // 5
// 逆变示例
List<Number> numbers = new ArrayList<>();
copy(numbers, integers); // 复制Integer到Number列表
System.out.println("复制后: " + numbers); // [3, 1, 4, 1, 5]
}
}
// 4. 类型系统的层次结构
public class TypeHierarchy {
static class Animal {}
static class Mammal extends Animal {}
static class Dog extends Mammal {}
static class Cat extends Mammal {}
public static void main(String[] args) {
// 数组协变
Mammal[] mammals = new Dog[5]; // 允许,但危险
// mammals[0] = new Cat(); // 运行时错误:ArrayStoreException
// 泛型不变(默认)
// List<Mammal> mammalList = new ArrayList<Dog>(); // 编译错误
// 使用通配符实现协变
List<? extends Mammal> mammalsCovariant = new ArrayList<Dog>(); // 允许
// 使用通配符实现逆变
List<? super Dog> dogsContravariant = new ArrayList<Animal>(); // 允许
// 复杂示例
Comparator<Animal> animalComparator = Comparator.comparing(Object::toString);
Comparator<Dog> dogComparator = animalComparator; // 逆变:Comparator<Animal>是Comparator<Dog>的子类型
List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
dogs.sort(dogComparator); // 可以用Animal比较器排序Dog列表
}
}
协变逆变总结:
| 特性 | 协变 | 逆变 | 不变 |
|---|---|---|---|
| 定义 | 子类型关系保持 | 子类型关系反转 | 无子类型关系 |
| Java数组 | 支持 | 不支持 | - |
| Java泛型 | 通过? extends实现 | 通过? super实现 | 默认行为 |
| 读取 | 安全 | 不安全 | 安全 |
| 写入 | 不安全 | 安全 | 安全 |
| PECS | Producer Extends | Consumer Super | 既生产又消费 |
实际应用场景:
- 协变:只读集合,如
List<? extends Number> - 逆变:只写集合,如
List<? super Integer> - 不变:需要同时读写,如
List<T>
本章总结:
- 泛型本质:参数化类型,编译时类型检查
- 类型擦除:为实现兼容性,运行时类型信息丢失
- 通配符:增加灵活性,
? extends用于读取,? super用于写入 - PECS原则:指导通配符使用的最佳实践
- 反射与泛型:通过
ParameterizedType获取泛型信息 - 协变逆变:理解类型系统的灵活性
- 最佳实践:合理使用泛型,平衡类型安全和代码灵活性
第五章:反射
5.1 什么是反射机制?反射机制的应用场景?
5.1.1 反射的基本概念和原理
反射是Java语言提供的一种在运行时动态获取和操作类、对象、方法、字段等信息的能力。
核心原理:
- Java程序运行时,每个类都有一个对应的Class对象
- Class对象包含了该类的所有结构信息(字段、方法、构造器等)
- 通过Class对象,可以在运行时动态操作该类的实例
反射的主要类:
Class<T>:代表类的类型信息Field:代表类的字段Method:代表类的方法Constructor<T>:代表类的构造方法
java
// 正常方式创建对象和调用方法
String str = "Hello";
int length = str.length();
// 反射方式
Class<?> clazz = String.class;
Method lengthMethod = clazz.getMethod("length");
Object result = lengthMethod.invoke(str); // 返回5
5.1.2 反射的优缺点
优点:
- 动态性:运行时获取类信息,动态创建对象、调用方法
- 灵活性:可以访问私有成员,突破封装限制
- 通用性:编写通用框架,如Spring、MyBatis等
- 扩展性:支持插件化架构和热部署
缺点:
- 性能低:比直接调用慢很多
- 安全隐患:可以访问私有数据,破坏封装
- 代码复杂:可读性差,调试困难
- 类型安全:编译时无法检查类型错误
对比表:
| 方面 | 直接调用 | 反射调用 |
|---|---|---|
| 性能 | 快(纳秒级) | 慢(微秒级) |
| 安全性 | 受访问控制保护 | 可绕过访问控制 |
| 代码可读性 | 好 | 差 |
| 编译时检查 | 有 | 无 |
| 灵活性 | 固定 | 高度灵活 |
5.1.3 反射的适用场景
适用场景:
- 框架开发:Spring IoC容器、MyBatis ORM框架
- 动态代理:AOP编程、RPC框架
- 注解处理:JUnit测试、Spring MVC
- 工具类库:JSON序列化(Jackson)、XML解析
- IDE插件:代码提示、自动补全
不适用场景:
- 性能敏感:核心业务逻辑、高频调用
- 简单需求:可以直接通过API实现的功能
- 需要严格封装:安全性要求高的组件
5.1.4 反射的安全性问题
安全问题:
- 破坏封装:可以访问和修改私有字段
- 执行任意代码:通过反射调用任意方法
- 绕过安全检查:使用
setAccessible(true)禁用访问检查
安全防护:
java
// 使用安全管理器限制反射
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) {
throw new SecurityException("禁止反射访问私有成员!");
}
}
};
System.setSecurityManager(securityManager);
5.1.5 反射的性能问题
性能差的原因:
- 动态解析:运行时解析类信息
- 安全检查:每次调用都要检查访问权限
- 方法调用:使用JNI或动态字节码,无法被JIT优化
- 自动装箱:参数和返回值可能需要装箱拆箱
5.2 如何通过反射创建对象、调用方法、访问字段?
5.2.1 获取Class对象的3种方式
方式对比:
| 方式 | 语法 | 适用场景 | 性能 | 异常处理 |
|---|---|---|---|---|
| 类名.class | String.class | 编译时已知类名 | 最好 | 不需要 |
| 对象.getClass() | obj.getClass() | 已有对象实例 | 好 | 不需要 |
| Class.forName() | Class.forName("java.lang.String") | 动态加载类 | 差 | 需要处理 |
示例:
java
// 1. 类名.class
Class<String> stringClass = String.class;
// 2. 对象.getClass()
String str = "hello";
Class<?> clazz1 = str.getClass();
// 3. Class.forName()
Class<?> clazz2 = Class.forName("java.lang.String");
// 验证三种方式获取的是同一个Class对象
System.out.println(stringClass == clazz1); // true
System.out.println(clazz1 == clazz2); // true
5.2.2 反射创建对象(无参、有参构造)
创建对象方式:
| 构造类型 | 获取方法 | 创建实例 | 注意事项 |
|---|---|---|---|
| 无参构造 | getConstructor() | newInstance() | 类必须有public无参构造 |
| 有参构造 | getConstructor(Class<?>...) | newInstance(Object...) | 参数类型匹配 |
| 私有构造 | getDeclaredConstructor(Class<?>...) | newInstance(Object...) | 需要setAccessible(true) |
示例:
java
class Person {
private String name;
private int age;
public Person() {
System.out.println("无参构造");
}
public Person(String name) {
this.name = name;
System.out.println("单参构造: " + name);
}
private Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("私有构造: " + name + ", " + age);
}
}
// 使用反射创建对象
Class<Person> clazz = Person.class;
// 1. 无参构造
Constructor<Person> cons1 = clazz.getConstructor();
Person p1 = cons1.newInstance();
// 2. 有参构造
Constructor<Person> cons2 = clazz.getConstructor(String.class);
Person p2 = cons2.newInstance("张三");
// 3. 私有构造
Constructor<Person> cons3 = clazz.getDeclaredConstructor(String.class, int.class);
cons3.setAccessible(true); // 关键:允许访问私有构造
Person p3 = cons3.newInstance("李四", 25);
5.2.3 反射调用方法(公有、私有、静态)
方法调用对比:
| 方法类型 | 获取方法 | 调用方式 | 注意事项 |
|---|---|---|---|
| 公有实例方法 | getMethod(name, paramTypes) | invoke(obj, args) | 需要对象实例 |
| 私有实例方法 | getDeclaredMethod(name, paramTypes) | invoke(obj, args) | 需要setAccessible(true) |
| 静态方法 | getMethod(name, paramTypes) | invoke(null, args) | 对象参数传null |
示例:
java
class Calculator {
public int add(int a, int b) {
return a + b;
}
private int multiply(int a, int b) {
return a * b;
}
public static int subtract(int a, int b) {
return a - b;
}
}
Calculator calc = new Calculator();
Class<?> clazz = calc.getClass();
// 1. 调用公有方法
Method addMethod = clazz.getMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calc, 10, 20); // 30
// 2. 调用私有方法
Method multiplyMethod = clazz.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);
int product = (int) multiplyMethod.invoke(calc, 10, 20); // 200
// 3. 调用静态方法
Method subtractMethod = clazz.getMethod("subtract", int.class, int.class);
int difference = (int) subtractMethod.invoke(null, 20, 10); // 10
5.2.4 反射访问字段(获取、设置值)
字段访问对比:
| 字段类型 | 获取方法 | 读写操作 | 注意事项 |
|---|---|---|---|
| 公有字段 | getField(name) | get(obj), set(obj, value) | 直接访问 |
| 私有字段 | getDeclaredField(name) | get(obj), set(obj, value) | 需要setAccessible(true) |
| 静态字段 | getField(name) | get(null), set(null, value) | 对象参数传null |
示例:
java
class Student {
public String name;
private int age;
public static String school = "清华大学";
}
Student stu = new Student();
stu.name = "张三";
Class<?> clazz = stu.getClass();
// 1. 访问公有字段
Field nameField = clazz.getField("name");
System.out.println(nameField.get(stu)); // 张三
nameField.set(stu, "李四");
System.out.println(stu.name); // 李四
// 2. 访问私有字段
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(stu, 25);
System.out.println(ageField.get(stu)); // 25
// 3. 访问静态字段
Field schoolField = clazz.getField("school");
System.out.println(schoolField.get(null)); // 清华大学
schoolField.set(null, "北京大学");
System.out.println(Student.school); // 北京大学
5.2.5 反射操作数组
数组操作API:
| 操作 | 方法 | 说明 |
|---|---|---|
| 创建数组 | Array.newInstance(componentType, length) | 动态创建数组 |
| 获取元素 | Array.get(array, index) | 获取指定位置元素 |
| 设置元素 | Array.set(array, index, value) | 设置指定位置元素 |
| 获取长度 | Array.getLength(array) | 获取数组长度 |
示例:
java
// 创建一维数组
String[] stringArray = (String[]) Array.newInstance(String.class, 5);
Array.set(stringArray, 0, "Java");
Array.set(stringArray, 1, "Python");
System.out.println(Array.get(stringArray, 0)); // Java
System.out.println(Array.getLength(stringArray)); // 5
// 创建二维数组
int[][] matrix = (int[][]) Array.newInstance(int.class, 3, 4);
int[] row = (int[]) Array.get(matrix, 0);
Array.set(row, 0, 100);
System.out.println(Array.get(row, 0)); // 100
5.2.6 反射操作注解
注解操作API:
| 操作 | 方法 | 说明 |
|---|---|---|
| 获取类注解 | getAnnotation(Class) | 获取指定类型的注解 |
| 获取所有注解 | getAnnotations() | 获取所有注解 |
| 检查注解存在 | isAnnotationPresent(Class) | 检查是否有指定注解 |
示例:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value();
}
@MyAnnotation("测试类")
class MyClass {
@Deprecated
public void oldMethod() {}
}
Class<MyClass> clazz = MyClass.class;
// 1. 获取类上的注解
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
if (annotation != null) {
System.out.println(annotation.value()); // 测试类
}
// 2. 检查注解存在
boolean hasAnnotation = clazz.isAnnotationPresent(MyAnnotation.class);
System.out.println(hasAnnotation); // true
// 3. 获取方法上的注解
Method method = clazz.getMethod("oldMethod");
Deprecated deprecated = method.getAnnotation(Deprecated.class);
System.out.println(deprecated != null); // true
5.3 为什么反射性能较差?如何优化?
5.3.1 反射性能差的原因
主要原因:
- 动态解析:运行时解析类信息,需要查找和验证
- 安全检查:每次调用都要检查访问权限
- 方法调用开销:需要通过JNI或动态字节码,无法被JIT优化
- 自动装箱:基本类型需要装箱为包装类型
性能对比测试:
java
class TestClass {
public int add(int a, int b) { return a + b; }
}
// 直接调用
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
test.add(i, i);
}
long directTime = System.nanoTime() - start;
// 反射调用(无优化)
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
Method method = TestClass.class.getMethod("add", int.class, int.class);
method.invoke(test, i, i);
}
long reflectionTime = System.nanoTime() - start;
System.out.println("直接调用: " + directTime + " ns");
System.out.println("反射调用: " + reflectionTime + " ns");
System.out.println("反射比直接调用慢" + (reflectionTime/directTime) + "倍");
5.3.2 反射性能优化方法
优化策略对比:
| 优化方法 | 原理 | 效果 | 实现难度 |
|---|---|---|---|
| 缓存反射对象 | 避免重复获取Class、Method等对象 | 显著提升 | 简单 |
| setAccessible | 禁用访问检查 | 中等提升 | 简单 |
| 避免自动装箱 | 使用基本类型参数 | 轻微提升 | 中等 |
| MethodHandle | 使用invokedynamic指令 | 大幅提升 | 中等 |
| 字节码增强 | 生成直接调用代码 | 最佳效果 | 困难 |
5.3.3 使用setAccessible优化
原理:setAccessible(true)禁用安全检查,提高后续访问性能
java
class Target {
private int value;
private int getValue() { return value; }
}
Target target = new Target();
Class<?> clazz = target.getClass();
// 未优化
Field field = clazz.getDeclaredField("value");
Method method = clazz.getDeclaredMethod("getValue");
long start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
field.setAccessible(true); // 每次都设置
field.set(target, i);
method.setAccessible(true); // 每次都设置
method.invoke(target);
}
long time1 = System.nanoTime() - start;
// 优化后
field.setAccessible(true); // 一次性设置
method.setAccessible(true); // 一次性设置
start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
field.set(target, i);
method.invoke(target);
}
long time2 = System.nanoTime() - start;
System.out.println("优化前: " + time1 + " ns");
System.out.println("优化后: " + time2 + " ns");
System.out.println("提升: " + (time1 - time2) + " ns");
5.3.4 缓存反射结果
缓存策略:将反射获取的Class、Method、Field等对象缓存起来,避免重复获取
java
import java.util.concurrent.ConcurrentHashMap;
class ReflectionCache {
private static final ConcurrentHashMap<String, Class<?>> classCache = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Method> methodCache = new ConcurrentHashMap<>();
public static Class<?> getClass(String className) throws Exception {
return classCache.computeIfAbsent(className, key -> {
try {
return Class.forName(key);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
}
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)
throws Exception {
String key = clazz.getName() + "#" + methodName;
for (int i = 0; paramTypes != null && i < paramTypes.length; i++) {
key += "#" + paramTypes[i].getName();
}
return methodCache.computeIfAbsent(key, k -> {
try {
Method method = clazz.getMethod(methodName, paramTypes);
method.setAccessible(true); // 一并优化
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
}
5.3.5 MethodHandle的替代方案
MethodHandle vs Reflection:
| 特性 | MethodHandle | Reflection |
|---|---|---|
| 性能 | 接近直接调用 | 较慢 |
| 类型安全 | 强类型检查 | 运行时检查 |
| 异常处理 | 不抛出检查异常 | 抛出检查异常 |
| 访问控制 | 遵守访问权限 | 可突破访问权限 |
示例:
java
import java.lang.invoke.*;
class Calculator {
public int add(int a, int b) { return a + b; }
}
Calculator calc = new Calculator();
// 反射方式
Method method = Calculator.class.getMethod("add", int.class, int.class);
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
method.invoke(calc, i, i);
}
long reflectionTime = System.nanoTime() - start;
// MethodHandle方式
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findVirtual(Calculator.class, "add",
MethodType.methodType(int.class, int.class, int.class));
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
handle.invoke(calc, i, i);
}
long handleTime = System.nanoTime() - start;
System.out.println("反射: " + reflectionTime + " ns");
System.out.println("MethodHandle: " + handleTime + " ns");
System.out.println("MethodHandle快" + (reflectionTime/handleTime) + "倍");
5.3.6 字节码增强技术
技术对比:
| 技术 | 原理 | 性能 | 使用难度 | 代表框架 |
|---|---|---|---|---|
| ASM | 直接操作字节码 | 最佳 | 困难 | Spring、MyBatis |
| Javassist | 源代码级别操作 | 中等 | 简单 | Hibernate |
| Byte Buddy | 流畅API封装 | 良好 | 中等 | Mockito |
| CGLIB | 基于ASM的封装 | 良好 | 中等 | Spring AOP |
5.4 反射的高级应用
5.4.1 动态代理的实现
动态代理原理:
- 在运行时动态创建代理类和对象
- 代理类实现指定接口
- 方法调用转发到InvocationHandler
示例:
java
interface UserService {
void addUser(String name);
void deleteUser(String name);
}
class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public void deleteUser(String name) {
System.out.println("删除用户: " + name);
}
}
class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【开始执行】" + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("【执行完成】耗时: " + (end - start) + "ms");
return result;
}
}
// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
proxy.addUser("张三");
proxy.deleteUser("李四");
5.4.2 注解处理器
运行时注解处理:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {
boolean enabled() default true;
String description() default "";
}
class TestClass {
@Test(description = "测试方法1")
public void testMethod1() {
System.out.println("执行testMethod1");
}
@Test(enabled = false)
public void testMethod2() {
System.out.println("执行testMethod2");
}
}
class TestRunner {
public static void runTests(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
Test annotation = method.getAnnotation(Test.class);
if (annotation.enabled()) {
System.out.println("执行测试: " + annotation.description());
method.invoke(obj);
} else {
System.out.println("跳过测试: " + method.getName());
}
}
}
}
}
// 使用
TestRunner.runTests(new TestClass());
5.4.3 反射与泛型的结合
泛型反射:获取泛型类型信息
java
import java.lang.reflect.*;
class GenericClass<T> {
private T value;
public List<T> getList() {
return new ArrayList<>();
}
}
class StringClass extends GenericClass<String> {
}
// 获取泛型信息
public class GenericReflectionDemo {
public static void main(String[] args) throws Exception {
// 获取泛型父类的类型参数
Type genericSuperclass = StringClass.class.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericSuperclass;
Type[] typeArgs = pt.getActualTypeArguments();
System.out.println("泛型参数: " + typeArgs[0]); // class java.lang.String
}
// 获取泛型方法的返回类型
Method method = GenericClass.class.getMethod("getList");
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) returnType;
System.out.println("返回类型: " + pt.getRawType()); // interface java.util.List
System.out.println("泛型参数: " + pt.getActualTypeArguments()[0]); // T
}
}
}
5.4.4 反射的安全管理
安全管理策略:
| 策略 | 实现方式 | 效果 | 适用场景 |
|---|---|---|---|
| SecurityManager | 安全管理器 | 全面控制 | 服务器应用 |
| AccessController | 访问控制器 | 精细控制 | 特权代码 |
| 自定义ClassLoader | 类加载器隔离 | 沙箱环境 | 插件系统 |
示例:
java
class SecureClass {
private String secret = "机密信息";
}
// 启用安全管理器
SecurityManager sm = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) {
throw new SecurityException("禁止反射访问私有成员!");
}
}
};
System.setSecurityManager(sm);
try {
SecureClass obj = new SecureClass();
Field field = SecureClass.class.getDeclaredField("secret");
field.setAccessible(true); // 抛出SecurityException
} catch (SecurityException e) {
System.out.println("安全异常: " + e.getMessage());
}
// 使用特权代码
String result = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
try {
SecureClass obj = new SecureClass();
Field field = SecureClass.class.getDeclaredField("secret");
field.setAccessible(true);
return (String) field.get(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
System.out.println("特权代码结果: " + result);
总结:
反射是Java强大的动态特性,但需要谨慎使用:
- 优先使用直接调用,避免不必要的反射
- 缓存反射对象,提高性能
- 注意安全问题,避免恶意代码利用反射
- 合理使用动态代理,实现AOP等高级特性
第六章:注解
6.1 注解的定义和分类
6.1.1 注解的基本概念
注解(Annotation) 是Java 5引入的一种元数据机制,用于为程序元素(类、方法、字段等)添加额外的信息和标记。
注解的核心特性:
- 元数据:为代码提供附加信息,不影响程序逻辑
- 标记机制:标识程序元素的特性或行为
- 可处理性:可通过编译器和反射进行解析处理
注解的基本语法:
java
// 定义注解
public @interface MyAnnotation {
String value();
}
// 使用注解
@MyAnnotation("测试")
public class TestClass {
}
6.1.2 元注解
元注解:用于定义注解的注解,Java提供5种标准元注解。
元注解对比表:
| 元注解 | 作用 | 参数 | 示例 | 说明 |
|---|---|---|---|---|
| @Target | 指定注解可应用的位置 | ElementType枚举 | @Target(ElementType.METHOD) | 控制注解使用范围 |
| @Retention | 指定注解保留时间 | RetentionPolicy枚举 | @Retention(RetentionPolicy.RUNTIME) | 控制注解生命周期 |
| @Documented | 包含在Javadoc中 | 无 | @Documented | 生成API文档时包含 |
| @Inherited | 子类继承父类注解 | 无 | @Inherited | 仅对类注解有效 |
| @Repeatable | 可重复使用 | 容器注解类 | @Repeatable(Schedules.class) | Java 8新增 |
示例详解:
java
import java.lang.annotation.*;
// 1. @Target - 指定注解使用位置
@Target({
ElementType.TYPE, // 类、接口、枚举
ElementType.FIELD, // 字段
ElementType.METHOD, // 方法
ElementType.PARAMETER, // 参数
ElementType.CONSTRUCTOR,// 构造方法
ElementType.LOCAL_VARIABLE, // 局部变量
ElementType.ANNOTATION_TYPE, // 注解类型
ElementType.PACKAGE, // 包
ElementType.TYPE_PARAMETER, // 类型参数(Java 8)
ElementType.TYPE_USE // 类型使用(Java 8)
})
@interface MyTargetAnnotation {
}
// 2. @Retention - 指定注解生命周期
@Retention(RetentionPolicy.SOURCE) // 源码级别,编译后丢弃
@interface SourceAnnotation {}
@Retention(RetentionPolicy.CLASS) // 类文件级别,运行时不保留
@interface ClassAnnotation {}
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
@interface RuntimeAnnotation {}
// 3. @Documented - 包含在Javadoc
@Documented
@interface DocumentedAnnotation {
String value();
}
// 4. @Inherited - 子类继承
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface InheritedAnnotation {
String value();
}
@InheritedAnnotation("父类")
class Parent {}
class Child extends Parent {} // Child也会拥有@InheritedAnnotation
// 5. @Repeatable - 可重复注解(Java 8)
@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Schedule {
String time();
}
@Retention(RetentionPolicy.RUNTIME)
@interface Schedules {
Schedule[] value(); // 容器注解
}
// 使用可重复注解
@Schedule(time = "9:00")
@Schedule(time = "15:00")
class Meeting {}
6.1.3 内置注解
Java内置注解分类:
| 分类 | 注解 | 作用 | 示例 |
|---|---|---|---|
| 编译检查 | @Override | 检查方法重写 | @Override public String toString() |
| @Deprecated | 标记过时元素 | @Deprecated public void oldMethod() | |
| @SuppressWarnings | 抑制编译器警告 | @SuppressWarnings("unchecked") | |
| @SafeVarargs | 泛型参数安全 | @SafeVarargs final void method(T... args) | |
| @FunctionalInterface | 函数式接口检查 | @FunctionalInterface interface Converter | |
| 资源管理 | @PostConstruct | 初始化后执行 | @PostConstruct void init() |
| @PreDestroy | 销毁前执行 | @PreDestroy void cleanup() | |
| 模块系统 | @Deprecated(forRemoval) | 计划移除 | @Deprecated(forRemoval=true) |
| @SuppressWarnings("removal") | 抑制移除警告 | @SuppressWarnings("removal") |
内置注解使用示例:
java
// 1. @Override - 方法重写检查
class Parent {
public void show() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override // 检查是否正确重写,防止拼写错误
public void show() {
System.out.println("Child");
}
// 错误示例:如果没有重写父类方法,编译错误
// @Override
// public void display() {} // 编译错误:方法不会重写或实现超类型的方法
}
// 2. @Deprecated - 标记过时
class OldAPI {
/**
* 使用新的newMethod代替
* @deprecated 从2.0版本开始过时,请使用newMethod
*/
@Deprecated(since = "2.0", forRemoval = false)
public void oldMethod() {
System.out.println("过时方法");
}
public void newMethod() {
System.out.println("新方法");
}
}
// 3. @SuppressWarnings - 抑制警告
class WarningExample {
@SuppressWarnings("unchecked") // 抑制未检查转换警告
public List<String> getList() {
return new ArrayList(); // 未指定泛型类型
}
@SuppressWarnings({"rawtypes", "unused"}) // 抑制多个警告
public void test() {
List list = new ArrayList(); // 使用原始类型
int unusedVar = 10; // 未使用的变量
}
}
// 4. @SafeVarargs - 安全泛型参数
class VarargsExample {
@SafeVarargs // 保证泛型可变参数的安全性
public final <T> List<T> asList(T... args) {
List<T> list = new ArrayList<>();
for (T arg : args) {
list.add(arg);
}
return list;
}
}
// 5. @FunctionalInterface - 函数式接口
@FunctionalInterface // 确保接口只有一个抽象方法
interface Converter<F, T> {
T convert(F from);
// 可以有默认方法和静态方法
default Converter<F, T> andThen(Converter<T, ?> after) {
return (F f) -> after.convert(convert(f));
}
static <T> Converter<T, T> identity() {
return t -> t;
}
}
6.1.4 注解的分类
按功能分类:
| 分类 | 特点 | 示例 | 使用场景 |
|---|---|---|---|
| 标记注解 | 没有元素的注解 | @Override、@Deprecated | 标记特定状态 |
| 单值注解 | 只有一个value元素 | @SuppressWarnings("value") | 简单配置 |
| 完整注解 | 有多个元素 | @RequestMapping(method=GET) | 复杂配置 |
| 元注解 | 定义注解的注解 | @Target、@Retention | 注解定义 |
| 组合注解 | 包含其他注解 | Spring的@SpringBootApplication | 简化配置 |
示例:
java
// 1. 标记注解(无元素)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MarkerAnnotation {
// 没有元素,仅用于标记
}
// 2. 单值注解(只有value元素)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SingleValueAnnotation {
String value(); // 只有value元素,使用时可以省略"value="
// 使用示例:
// @SingleValueAnnotation("test")
// @SingleValueAnnotation(value = "test") // 等价
}
// 3. 完整注解(多个元素)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface FullAnnotation {
String name();
int priority() default 1;
String[] tags() default {};
Class<?> type() default Object.class;
}
// 4. 组合注解示例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MarkerAnnotation
@interface CombinedAnnotation {
SingleValueAnnotation config() default @SingleValueAnnotation("default");
FullAnnotation[] details() default {};
}
6.2 自定义注解的实现
6.2.1 定义注解的语法
注解定义规则:
- 使用
@interface关键字 - 可以包含元素(类似方法声明)
- 元素可以有默认值
- 可以添加元注解
基本语法:
java
[访问修饰符] @interface 注解名 {
类型 元素名() [default 默认值];
类型 元素名() [default 默认值];
// ...
}
示例:
java
// 简单注解定义
public @interface Author {
String name();
String email() default "";
String date();
}
// 使用示例
@Author(
name = "张三",
email = "zhangsan@example.com",
date = "2024-01-15"
)
public class MyClass {
@Author(name = "李四", date = "2024-01-16")
public void method() {
}
}
6.2.2 注解元素的类型限制
允许的元素类型:
| 类型 | 示例 | 说明 |
|---|---|---|
| 基本类型 | int value() | byte、short、int、long、float、double、char、boolean |
| String | String name() | 字符串类型 |
| Class | Class<?> type() | 类类型 |
| Enum | Status status() | 枚举类型 |
| Annotation | Author author() | 注解类型 |
| 数组 | String[] tags() | 以上类型的数组 |
限制:
- 不能使用
null作为默认值 - 不能是泛型类型
- 不能是
void类型
类型示例:
java
// 枚举定义
enum Status {
ACTIVE, INACTIVE, PENDING
}
// 各种类型的注解元素
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeExample {
// 基本类型
int id();
double score() default 0.0;
boolean enabled() default true;
// String类型
String name();
// Class类型
Class<?> targetClass();
// 枚举类型
Status status();
// 注解类型
Author author();
// 数组类型
String[] tags();
int[] numbers() default {1, 2, 3};
Class<?>[] classes() default {};
// 默认值不能是null
// String description() default null; // 编译错误
}
// 使用示例
@TypeExample(
id = 1,
name = "测试",
targetClass = String.class,
status = Status.ACTIVE,
author = @Author(name = "王五", date = "2024-01-17"),
tags = {"java", "annotation"}
)
class ExampleClass {
}
6.2.3 注解的默认值
默认值规则:
- 使用
default关键字指定默认值 - 默认值必须是编译时常量
- 数组默认值使用
{}表示空数组
示例:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DefaultValueExample {
// 各种类型的默认值
int count() default 0;
String name() default "default";
boolean enabled() default true;
Class<?> type() default Object.class;
Status status() default Status.PENDING;
String[] tags() default {};
int[] values() default {1, 2, 3};
// 注解元素的默认值
Author author() default @Author(name = "默认作者", date = "2024-01-01");
}
// 使用默认值
class DefaultValueUsage {
@DefaultValueExample // 使用所有默认值
public void method1() {}
@DefaultValueExample(
name = "自定义",
count = 10,
tags = {"tag1", "tag2"}
)
public void method2() {}
}
6.2.4 注解的继承性
继承规则:
- 默认情况下,注解不会被继承
- 使用
@Inherited元注解的类级别注解可以被继承 - 方法、字段等元素上的注解不会被继承
继承示例:
java
// 可继承的注解
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface InheritableAnnotation {
String value();
}
// 不可继承的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface NonInheritableAnnotation {
String value();
}
// 父类
@InheritableAnnotation("父类的可继承注解")
@NonInheritableAnnotation("父类的不可继承注解")
class Parent {
@InheritableAnnotation("方法上的注解") // 不会被子类继承
public void method() {}
}
// 子类
class Child extends Parent {
// 自动继承@InheritableAnnotation,但不继承@NonInheritableAnnotation
// 也不会继承方法上的@InheritableAnnotation
}
// 测试继承性
public class InheritanceTest {
public static void main(String[] args) {
Class<Child> childClass = Child.class;
// 检查类上的注解
System.out.println("Child是否有@InheritableAnnotation: " +
childClass.isAnnotationPresent(InheritableAnnotation.class)); // true
System.out.println("Child是否有@NonInheritableAnnotation: " +
childClass.isAnnotationPresent(NonInheritableAnnotation.class)); // false
// 检查父类方法上的注解是否被继承
try {
Method method = Parent.class.getMethod("method");
System.out.println("Parent.method是否有@InheritableAnnotation: " +
method.isAnnotationPresent(InheritableAnnotation.class)); // true
Method childMethod = Child.class.getMethod("method");
System.out.println("Child.method是否有@InheritableAnnotation: " +
childMethod.isAnnotationPresent(InheritableAnnotation.class)); // false
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
6.3 注解处理器
6.3.1 编译时注解处理器
编译时处理:在编译期间处理注解,生成额外的代码或进行编译检查。
特点:
- 处理时机:Java编译期间
- 技术:APT(Annotation Processing Tool)
- 输出:生成新的Java源文件
- 应用:Lombok、Dagger、MapStruct等
工作流程:
text
源代码 → 编译器 → 注解处理器 → 生成新文件 → 编译器 → 字节码
6.3.2 运行时注解处理器
运行时处理:在程序运行期间通过反射处理注解。
特点:
- 处理时机:程序运行期间
- 技术:反射机制
- 输出:影响程序运行行为
- 应用:Spring框架、JUnit测试等
对比表:
| 方面 | 编译时处理 | 运行时处理 |
|---|---|---|
| 处理时机 | 编译期间 | 运行期间 |
| 技术实现 | APT | 反射 |
| 性能影响 | 编译时一次性处理 | 运行时持续开销 |
| 灵活性 | 有限,生成固定代码 | 高,动态处理 |
| 典型应用 | 代码生成工具 | 框架配置处理 |
| 可见性 | 需要RetentionPolicy.SOURCE | 需要RetentionPolicy.RUNTIME |
6.3.3 APT(Annotation Processing Tool)
APT工作流程:
- 编译器解析源代码,构建抽象语法树
- 调用注册的注解处理器
- 处理器处理注解元素
- 可生成新的源代码文件
- 编译器重新解析生成的文件
处理器注册:
在META-INF/services/javax.annotation.processing.Processor文件中注册处理器类。
6.3.4 注解处理器的编写
简单注解处理器示例:
java
// 1. 定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) // 源码级别,编译后丢弃
public @interface GenerateToString {
boolean includeFieldNames() default true;
}
// 2. 注解处理器
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.tools.*;
import java.io.*;
import java.util.*;
@SupportedAnnotationTypes("GenerateToString")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GenerateToStringProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(GenerateToString.class)) {
if (element.getKind() == ElementKind.CLASS) {
TypeElement classElement = (TypeElement) element;
GenerateToString annotation = classElement.getAnnotation(GenerateToString.class);
generateToStringClass(classElement, annotation.includeFieldNames());
}
}
return true;
}
private void generateToStringClass(TypeElement classElement, boolean includeFieldNames) {
String className = classElement.getSimpleName().toString();
String packageName = processingEnv.getElementUtils()
.getPackageOf(classElement).toString();
StringBuilder code = new StringBuilder();
// 生成包声明
if (!packageName.isEmpty()) {
code.append("package ").append(packageName).append(";\n\n");
}
// 生成导入
code.append("import java.lang.StringBuilder;\n\n");
// 生成类定义
code.append("public class ").append(className).append("ToStringHelper {\n\n");
// 生成toString方法
code.append(" public static String toString(").append(className).append(" obj) {\n");
code.append(" StringBuilder sb = new StringBuilder();\n");
code.append(" sb.append("").append(className).append("{");\n");
// 遍历字段
boolean firstField = true;
for (Element member : processingEnv.getElementUtils()
.getAllMembers(classElement)) {
if (member.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) member;
String fieldName = field.getSimpleName().toString();
if (!firstField) {
code.append(" sb.append(", ");\n");
}
if (includeFieldNames) {
code.append(" sb.append("").append(fieldName).append("=");\n");
}
code.append(" sb.append(obj.").append(fieldName).append(");\n");
firstField = false;
}
}
code.append(" sb.append("}");\n");
code.append(" return sb.toString();\n");
code.append(" }\n");
code.append("}\n");
// 写入文件
try {
JavaFileObject file = processingEnv.getFiler()
.createSourceFile(packageName + "." + className + "ToStringHelper");
try (Writer writer = file.openWriter()) {
writer.write(code.toString());
}
} catch (IOException e) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR, "生成文件失败: " + e.getMessage());
}
}
}
6.3.5 注解在AOP中的应用
AOP中的注解使用:
java
// 1. 定义AOP注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default "";
Level level() default Level.INFO;
enum Level {
DEBUG, INFO, WARN, ERROR
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MeasureTime {
String name() default "";
}
// 2. 业务类使用注解
class BusinessService {
@LogExecution("执行用户查询")
@MeasureTime(name = "queryUser")
public User queryUser(String userId) {
// 业务逻辑
return new User(userId, "张三");
}
@LogExecution(value = "保存用户", level = LogExecution.Level.WARN)
public void saveUser(User user) {
// 业务逻辑
}
}
// 3. AOP处理器
class AOPHandler {
public static Object invokeWithAOP(Object target, Method method, Object[] args)
throws Throwable {
long startTime = 0;
// 处理@MeasureTime
if (method.isAnnotationPresent(MeasureTime.class)) {
startTime = System.currentTimeMillis();
}
// 处理@LogExecution
if (method.isAnnotationPresent(LogExecution.class)) {
LogExecution logAnnotation = method.getAnnotation(LogExecution.class);
System.out.println("[" + logAnnotation.level() + "] " +
logAnnotation.value() + " - 开始执行");
}
// 执行原方法
Object result = method.invoke(target, args);
// 执行后处理
if (method.isAnnotationPresent(LogExecution.class)) {
LogExecution logAnnotation = method.getAnnotation(LogExecution.class);
System.out.println("[" + logAnnotation.level() + "] " +
logAnnotation.value() + " - 执行完成");
}
if (method.isAnnotationPresent(MeasureTime.class)) {
long endTime = System.currentTimeMillis();
MeasureTime timeAnnotation = method.getAnnotation(MeasureTime.class);
System.out.println("方法 " + timeAnnotation.name() + " 执行时间: " +
(endTime - startTime) + "ms");
}
return result;
}
}
// 4. 使用AOP
public class AOPExample {
public static void main(String[] args) throws Exception {
BusinessService service = new BusinessService();
Method queryMethod = BusinessService.class.getMethod("queryUser", String.class);
// 通过AOP调用
User user = (User) AOPHandler.invokeWithAOP(service, queryMethod,
new Object[]{"123"});
System.out.println("查询结果: " + user);
}
}
6.4 注解的实际应用
6.4.1 自定义注解的实际案例
案例1:数据验证注解
java
// 验证注解定义
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message() default "字段不能为空";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Size {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "长度不符合要求";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Email {
String message() default "邮箱格式不正确";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pattern {
String regexp();
String message() default "格式不符合要求";
}
// 验证器
class Validator {
public static List<String> validate(Object obj) {
List<String> errors = new ArrayList<>();
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
Object value = field.get(obj);
// 检查@NotNull
if (field.isAnnotationPresent(NotNull.class)) {
NotNull annotation = field.getAnnotation(NotNull.class);
if (value == null || (value instanceof String && ((String) value).isEmpty())) {
errors.add(field.getName() + ": " + annotation.message());
}
}
// 检查@Size
if (field.isAnnotationPresent(Size.class)) {
Size annotation = field.getAnnotation(Size.class);
if (value instanceof String) {
String str = (String) value;
if (str.length() < annotation.min() || str.length() > annotation.max()) {
errors.add(field.getName() + ": " + annotation.message());
}
}
}
// 检查@Email
if (field.isAnnotationPresent(Email.class)) {
Email annotation = field.getAnnotation(Email.class);
if (value instanceof String) {
String email = (String) value;
if (!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
errors.add(field.getName() + ": " + annotation.message());
}
}
}
// 检查@Pattern
if (field.isAnnotationPresent(Pattern.class)) {
Pattern annotation = field.getAnnotation(Pattern.class);
if (value instanceof String) {
String str = (String) value;
if (!str.matches(annotation.regexp())) {
errors.add(field.getName() + ": " + annotation.message());
}
}
}
} catch (IllegalAccessException e) {
errors.add("无法访问字段: " + field.getName());
}
}
return errors;
}
}
// 用户实体
class User {
@NotNull(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@NotNull
@Email
private String email;
@Pattern(regexp = "^1[3-9]\d{9}$", message = "手机号格式不正确")
private String phone;
// 构造方法、getter、setter
public User(String username, String email, String phone) {
this.username = username;
this.email = email;
this.phone = phone;
}
}
// 使用验证器
public class ValidationDemo {
public static void main(String[] args) {
// 测试数据验证
User user1 = new User("", "invalid-email", "123");
List<String> errors = Validator.validate(user1);
System.out.println("验证错误:");
errors.forEach(System.out::println);
// 正确数据
User user2 = new User("张三", "zhangsan@example.com", "13800138000");
errors = Validator.validate(user2);
System.out.println("验证通过: " + errors.isEmpty());
}
}
案例2:API权限控制注解
java
// 权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String[] value(); // 需要的权限列表
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String value();
}
// 权限上下文
class SecurityContext {
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setCurrentUser(User user) {
currentUser.set(user);
}
public static User getCurrentUser() {
return currentUser.get();
}
public static void clear() {
currentUser.remove();
}
}
// 权限拦截器
class PermissionInterceptor {
public static Object intercept(Object target, Method method, Object[] args)
throws Throwable {
User currentUser = SecurityContext.getCurrentUser();
if (currentUser == null) {
throw new SecurityException("用户未登录");
}
// 检查角色权限
if (method.isAnnotationPresent(RequireRole.class)) {
RequireRole roleAnnotation = method.getAnnotation(RequireRole.class);
String requiredRole = roleAnnotation.value();
if (!currentUser.getRoles().contains(requiredRole)) {
throw new SecurityException("需要角色: " + requiredRole);
}
}
// 检查权限
if (method.isAnnotationPresent(RequirePermission.class)) {
RequirePermission permAnnotation = method.getAnnotation(RequirePermission.class);
String[] requiredPerms = permAnnotation.value();
for (String requiredPerm : requiredPerms) {
if (!currentUser.getPermissions().contains(requiredPerm)) {
throw new SecurityException("需要权限: " + requiredPerm);
}
}
}
// 权限检查通过,执行方法
return method.invoke(target, args);
}
}
// 业务服务
class UserService {
@RequireRole("ADMIN")
public void deleteUser(String userId) {
System.out.println("删除用户: " + userId);
}
@RequirePermission({"USER_READ", "USER_WRITE"})
public void updateUser(User user) {
System.out.println("更新用户: " + user);
}
public void getUserInfo(String userId) {
System.out.println("获取用户信息: " + userId);
}
}
// 用户实体
class User {
private String id;
private Set<String> roles = new HashSet<>();
private Set<String> permissions = new HashSet<>();
public User(String id, Set<String> roles, Set<String> permissions) {
this.id = id;
this.roles = roles;
this.permissions = permissions;
}
// getter方法
public Set<String> getRoles() { return roles; }
public Set<String> getPermissions() { return permissions; }
}
// 测试
public class PermissionDemo {
public static void main(String[] args) throws Exception {
UserService service = new UserService();
// 设置普通用户
Set<String> userRoles = new HashSet<>(Arrays.asList("USER"));
Set<String> userPerms = new HashSet<>(Arrays.asList("USER_READ"));
User user = new User("user1", userRoles, userPerms);
SecurityContext.setCurrentUser(user);
try {
// 普通用户可以查看
Method getMethod = UserService.class.getMethod("getUserInfo", String.class);
PermissionInterceptor.intercept(service, getMethod, new Object[]{"123"});
System.out.println("查看用户信息成功");
// 普通用户不能删除(需要ADMIN角色)
Method deleteMethod = UserService.class.getMethod("deleteUser", String.class);
PermissionInterceptor.intercept(service, deleteMethod, new Object[]{"123"});
} catch (SecurityException e) {
System.out.println("权限不足: " + e.getMessage());
} finally {
SecurityContext.clear();
}
}
}
6.4.2 注解的工作流程
注解处理完整流程:
text
定义阶段 -> 使用阶段 -> 处理阶段 -> 影响阶段
详细流程:
-
定义阶段
java
// 定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); } -
使用阶段
java
// 使用注解 class MyClass { @MyAnnotation("测试") public void myMethod() { } } -
处理阶段
- 编译时处理:APT处理器生成代码
- 运行时处理:通过反射读取注解
java
// 运行时处理 Method method = MyClass.class.getMethod("myMethod"); if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println("注解值: " + annotation.value()); } -
影响阶段
- 改变编译行为:生成额外代码
- 改变运行行为:执行额外逻辑
- 提供元数据:供框架使用
注解生命周期图:
text
源码文件(.java)
↓ 编译(@Retention(SOURCE))
编译时处理
↓ 编译(@Retention(CLASS))
字节码文件(.class)
↓ 类加载
运行时处理(@Retention(RUNTIME))
↓ 执行
影响程序行为
总结:
- 注解是Java强大的元数据机制
- 合理使用注解可以大大提高代码的可读性和可维护性
- 编译时注解用于代码生成,运行时注解用于动态处理
- 实际应用中,注解常用于配置、验证、AOP等场景
- 理解注解的原理和工作流程有助于更好地使用框架和设计系统
第七章:内部类与枚举
7.1 内部类的分类
7.1.1 成员内部类
解释:定义在类中,方法外的内部类,可以访问外部类的所有成员(包括私有成员)。
示例:
java
public class Outer {
private String outerField = "外部类字段";
class Inner {
public void print() {
System.out.println("访问外部类字段: " + outerField);
}
}
}
7.1.2 局部内部类
解释:定义在方法或代码块中的内部类,作用域仅限于定义它的方法或代码块。
示例:
java
public class Outer {
public void method() {
class LocalInner {
public void print() {
System.out.println("局部内部类");
}
}
LocalInner inner = new LocalInner();
inner.print();
}
}
7.1.3 匿名内部类
解释:没有名字的内部类,通常用于实现接口或继承类,只能使用一次。
示例:
java
interface Greeting {
void greet();
}
public class Test {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello, 匿名内部类!");
}
};
greeting.greet();
}
}
7.1.4 静态内部类
解释:使用static修饰的内部类,不能直接访问外部类的非静态成员。
示例:
java
public class Outer {
private static String staticField = "静态字段";
static class StaticInner {
public void print() {
System.out.println("只能访问外部类的静态字段: " + staticField);
}
}
}
7.1.5 各种内部类的区别和选择
| 类型 | 访问权限 | 内存占用 | 使用场景 |
|---|---|---|---|
| 成员内部类 | 可访问外部类所有成员 | 持有外部类引用 | 需要紧密耦合的场景 |
| 局部内部类 | 只能访问final/有效final变量 | 方法内有效 | 方法内部需要特殊实现的场景 |
| 匿名内部类 | 同局部内部类 | 一次性使用 | 事件监听、简单回调 |
| 静态内部类 | 只能访问外部类静态成员 | 不持有外部类引用 | 不需要访问外部实例的场景 |
选择原则:
- 需要访问外部实例 → 成员内部类
- 不需要访问外部实例 → 静态内部类
- 只使用一次 → 匿名内部类
- 方法内专用 → 局部内部类
7.2 内部类的原理和特性
7.2.1 内部类与外部类的访问关系
解释:编译器会为内部类自动添加一个指向外部类实例的引用。
示例:
java
// 编译后相当于
public class Outer$Inner {
private final Outer this$0; // 编译器添加的引用
Outer$Inner(Outer outer) {
this.this$0 = outer;
}
}
7.2.2 内部类的字节码分析
总结:
- 每个内部类都会生成独立的.class文件
- 命名格式:
外部类名$内部类名.class - 匿名内部类:
外部类名$数字.class
7.2.3 内部类的内存泄漏问题
问题:内部类持有外部类引用,如果内部类对象生命周期长于外部类,会导致外部类无法被回收。
解决方案:
java
// 1. 使用静态内部类
static class StaticInner {}
// 2. 使用弱引用
class Inner {
private WeakReference<Outer> outerRef;
}
7.2.4 内部类的序列化
注意事项:
- 内部类默认没有无参构造器
- 隐式持有外部类引用
- 建议使用静态内部类进行序列化
7.2.5 内部类的最佳实践
- 优先使用静态内部类
- 避免在频繁调用的方法中使用匿名内部类
- 注意内存泄漏问题
- 合理使用访问权限
7.3 枚举类型
7.3.1 枚举的定义和使用
解释:枚举是一种特殊的类,表示一组固定的常量。
示例:
java
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// 使用
Day today = Day.MONDAY;
7.3.2 枚举的底层实现原理
原理:
- 枚举本质是继承
java.lang.Enum的final类 - 每个枚举常量都是类的实例
- 私有构造器,防止外部实例化
编译后相当于:
java
public final class Day extends Enum<Day> {
public static final Day MONDAY = new Day("MONDAY", 0);
// ... 其他常量
}
7.3.3 枚举的常用方法
java
Day day = Day.MONDAY;
// 获取名字
String name = day.name(); // "MONDAY"
// 获取序号
int ordinal = day.ordinal(); // 0
// 转换为字符串
String str = day.toString();
// 根据名字获取枚举
Day value = Day.valueOf("MONDAY");
// 获取所有枚举值
Day[] values = Day.values();
7.3.4 枚举与常量类的对比
| 特性 | 枚举 | 常量类 |
|---|---|---|
| 类型安全 | ✅ | ❌ |
| 可遍历 | ✅ | ❌ |
| 可添加方法 | ✅ | ❌ |
| 序列化安全 | ✅ | ❌ |
| 编译时检查 | ✅ | ❌ |
示例对比:
java
// 常量类(不推荐)
class Color {
public static final int RED = 1;
public static final int GREEN = 2;
}
// 枚举(推荐)
enum Color {
RED, GREEN, BLUE
}
7.3.5 枚举的高级用法
枚举实现接口:
java
interface Operation {
double apply(double x, double y);
}
enum BasicOperation implements Operation {
PLUS {
public double apply(double x, double y) { return x + y; }
},
MINUS {
public double apply(double x, double y) { return x - y; }
};
}
枚举集合:
java
EnumSet<Day> weekdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
7.3.6 枚举的设计模式应用
单例模式:
java
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("单例方法");
}
}
// 使用
Singleton.INSTANCE.doSomething();
优点:线程安全、防止反射攻击、自动序列化
7.4 枚举与注解的结合
7.4.1 枚举作为注解参数
java
@interface RequestMapping {
HttpMethod[] method() default {};
}
enum HttpMethod {
GET, POST, PUT, DELETE
}
// 使用
@RequestMapping(method = {HttpMethod.GET, HttpMethod.POST})
public class MyController {}
7.4.2 枚举在策略模式中的应用
java
enum Calculator {
ADD {
public int calculate(int a, int b) { return a + b; }
},
SUBTRACT {
public int calculate(int a, int b) { return a - b; }
};
public abstract int calculate(int a, int b);
}
7.4.3 枚举的状态机实现
java
enum State {
NEW {
public State next() { return RUNNING; }
},
RUNNING {
public State next() { return STOPPED; }
},
STOPPED {
public State next() { return this; }
};
public abstract State next();
}