01.01.04 Java基础篇|泛型、注解与反射实战
导读
- 适用人群:掌握基础语法,想深入理解泛型、注解、反射三驾马车的开发者。
- 目标:掌握编译期安全、运行时动态扩展与 AOP 的原理与用法。
- 阅读建议:遵循"泛型理论 → 注解模型 → 反射 API → 策略实战 → 面试问答"的顺序。
核心知识架构
泛型(Generics)深度解析
类型擦除(Type Erasure)原理
核心机制:
java
// 源代码
List<String> list = new ArrayList<>();
list.add("hello");
// 编译后(类型擦除)
List list = new ArrayList(); // 类型参数被擦除
list.add("hello"); // 插入时自动转换
String s = (String) list.get(0); // 读取时强制转换
类型擦除的规则:
- 无界类型参数 :
<T>→Object - 有界类型参数 :
<T extends Number>→Number - 通配符 :
<?>→Object,<? extends Number>→Number
源码验证:
java
// 编译前
public class GenericClass<T> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}
// 编译后(通过 javap -c 查看字节码)
// 实际类型被擦除为 Object
public class GenericClass {
private Object value;
public void setValue(Object value) { this.value = value; }
public Object getValue() { return value; }
}
类型擦除带来的问题:
java
// 问题1:无法创建泛型数组
// List<String>[] array = new List<String>[10]; // 编译错误
// 问题2:无法使用 instanceof
// if (obj instanceof List<String>) { } // 编译错误
// 问题3:无法重载(类型擦除后签名相同)
public void method(List<String> list) { }
public void method(List<Integer> list) { } // 编译错误:方法签名重复
通配符与边界深度解析
通配符(Wildcard)的基本概念:
通配符 ? 表示未知类型,用于增加泛型的灵活性。Java 提供了三种通配符:
?:无界通配符(Unbounded Wildcard)? extends T:上界通配符(Upper Bounded Wildcard)? super T:下界通配符(Lower Bounded Wildcard)
上界通配符(<? extends T>)详解
基本语法:
java
List<? extends Number> numbers;
含义 :? 是 Number 或其子类型的未知类型
类型关系图:
Number (上界)
├── Integer
├── Double
├── Float
└── Long
核心特性:读取安全,写入受限
1. 读取操作(安全):
java
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(1); // 初始化时可以添加
// 可以安全读取为 Number 或其父类型
Number n = numbers.get(0); // ✅ 安全:Integer 是 Number 的子类
Object obj = numbers.get(0); // ✅ 安全:Number 是 Object 的子类
// Integer i = numbers.get(0); // ❌ 编译错误:可能是 Double
// 原因:编译器只知道是 Number 的子类型,不知道具体是哪个
// 如果实际是 List<Double>,强制转换为 Integer 会出错
2. 写入操作(受限):
java
List<? extends Number> numbers = new ArrayList<Integer>();
// ❌ 编译错误:不能添加任何元素(除了 null)
// numbers.add(new Integer(1)); // 编译错误
// numbers.add(new Double(1.0)); // 编译错误
numbers.add(null); // ✅ 唯一可以添加的:null(所有类型的子类型)
// 原因分析:
// 假设 numbers 实际指向 List<Double>
// 如果允许 numbers.add(new Integer(1))
// 那么 List<Double> 中就会混入 Integer,破坏类型安全
3. 类型擦除后的实际类型:
java
// 编译前
List<? extends Number> numbers = new ArrayList<Integer>();
// 类型擦除后
List numbers = new ArrayList(); // 擦除为 List
// 读取时:Number n = (Number) numbers.get(0);
4. 实际应用场景:
java
// 场景1:只读取集合元素
public void printNumbers(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n.doubleValue()); // 可以调用 Number 的方法
}
// 不能修改集合
}
// 场景2:从集合中提取数据
public List<Number> extractNumbers(List<? extends Number> source) {
List<Number> result = new ArrayList<>();
for (Number n : source) {
result.add(n); // ✅ 可以添加:Number 是 Number 的超类型
}
return result;
}
// 场景3:方法返回只读视图
public List<? extends Number> getReadOnlyNumbers() {
return Collections.unmodifiableList(new ArrayList<Integer>());
}
5. 上界通配符的赋值规则:
java
// ✅ 可以赋值:具体类型是上界的子类型
List<? extends Number> list1 = new ArrayList<Integer>();
List<? extends Number> list2 = new ArrayList<Double>();
List<? extends Number> list3 = new ArrayList<Float>();
// ❌ 不能赋值:具体类型不是上界的子类型
// List<? extends Number> list4 = new ArrayList<String>(); // 编译错误
// ✅ 可以赋值:更具体的上界通配符
List<? extends Integer> intList = new ArrayList<Integer>();
List<? extends Number> numList = intList; // ✅ Integer extends Number
下界通配符(<? super T>)详解
基本语法:
java
List<? super Integer> numbers;
含义 :? 是 Integer 或其父类型的未知类型
类型关系图:
Object (最上界)
└── Number
└── Integer (下界)
└── ...
核心特性:写入安全,读取受限
1. 写入操作(安全):
java
List<? super Integer> numbers = new ArrayList<Number>();
// ✅ 可以添加下界类型及其子类型
numbers.add(new Integer(1)); // ✅ 安全:Integer 是 Integer 的子类型
// numbers.add(new Number()); // ❌ 编译错误:Number 是 Integer 的父类型
// numbers.add(new Object()); // ❌ 编译错误:Object 是 Integer 的父类型
// 原因分析:
// 假设 numbers 实际指向 List<Number>
// 添加 Integer 是安全的,因为 Integer 是 Number 的子类型
// 但添加 Number 或 Object 不安全,因为实际可能是 List<Integer>
2. 读取操作(受限):
java
List<? super Integer> numbers = new ArrayList<Number>();
numbers.add(new Integer(1));
// ❌ 编译错误:不能读取为具体类型
// Integer i = numbers.get(0); // 编译错误:可能是 Number 或 Object
// Number n = numbers.get(0); // 编译错误:可能是 Object
// ✅ 只能读取为 Object(所有类型的父类型)
Object obj = numbers.get(0); // ✅ 安全:任何类型都是 Object 的子类型
// 原因分析:
// 假设 numbers 实际指向 List<Object>
// 如果允许 Integer i = numbers.get(0)
// 但实际存储的是 String,强制转换会出错
3. 类型擦除后的实际类型:
java
// 编译前
List<? super Integer> numbers = new ArrayList<Number>();
// 类型擦除后
List numbers = new ArrayList(); // 擦除为 List
// 读取时:Object obj = (Object) numbers.get(0);
4. 实际应用场景:
java
// 场景1:只写入集合元素
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
// 不能读取具体类型
}
// 场景2:将数据写入集合
public void copyNumbers(List<? extends Number> source,
List<? super Number> dest) {
for (Number n : source) {
dest.add(n); // ✅ 可以添加:Number 是 Number 的子类型
}
}
// 场景3:回调函数参数
public void processIntegers(List<? super Integer> processor) {
processor.add(100);
processor.add(200);
}
5. 下界通配符的赋值规则:
java
// ✅ 可以赋值:具体类型是下界的父类型
List<? super Integer> list1 = new ArrayList<Integer>();
List<? super Integer> list2 = new ArrayList<Number>();
List<? super Integer> list3 = new ArrayList<Object>();
// ❌ 不能赋值:具体类型不是下界的父类型
// List<? super Integer> list4 = new ArrayList<Double>(); // 编译错误
// ✅ 可以赋值:更具体的下界通配符
List<? super Number> numList = new ArrayList<Object>();
List<? super Integer> intList = numList; // ✅ Number super Integer
PECS 原则(Producer Extends, Consumer Super)深度解析
PECS 原则的核心思想:
- Producer(生产者) :只读取数据,使用
extends - Consumer(消费者) :只写入数据,使用
super
为什么叫 PECS?
- P roducer Extends:生产者使用 extends
- C onsumer Super:消费者使用 super
1. Producer Extends 示例:
java
// 生产者:从集合中读取数据
public void printAll(List<? extends Number> numbers) {
// 只读取,不修改
for (Number n : numbers) {
System.out.println(n);
}
}
// 生产者:从集合中提取数据
public <T> List<T> filter(List<? extends T> source, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : source) { // 读取
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
2. Consumer Super 示例:
java
// 消费者:向集合中写入数据
public void fillList(List<? super Integer> list) {
// 只写入,不读取具体类型
list.add(1);
list.add(2);
list.add(3);
}
// 消费者:将数据写入集合
public <T> void addAll(List<? super T> dest, T[] items) {
for (T item : items) {
dest.add(item); // 写入
}
}
3. PECS 原则的实际应用:Collections.copy:
java
// JDK 源码
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
// src 是生产者(读取),使用 extends
// dest 是消费者(写入),使用 super
ListIterator<? super T> di = dest.listIterator();
ListIterator<? extends T> si = src.listIterator();
for (int i = 0; i < srcSize; i++) {
di.next();
di.set(si.next()); // 从 src 读取,写入 dest
}
}
// 使用示例
List<Integer> source = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
Collections.copy(dest, source); // ✅ 正确
4. 违反 PECS 原则的后果:
java
// ❌ 错误:生产者使用了 super
public void printNumbers(List<? super Number> numbers) {
// 问题:只能读取为 Object,失去了类型信息
for (Object obj : numbers) {
// 不能调用 Number 的方法
// System.out.println(obj.doubleValue()); // 编译错误
}
}
// ❌ 错误:消费者使用了 extends
public void addNumbers(List<? extends Number> numbers) {
// 问题:不能添加任何元素(除了 null)
// numbers.add(new Integer(1)); // 编译错误
}
5. PECS 原则的记忆技巧:
- Extends = 上限 = 只能读:extends 表示上限,只能读取为上限类型
- Super = 下限 = 只能写:super 表示下限,只能写入下限类型及其子类型
多重边界(Multiple Bounds)详解
基本语法:
java
<T extends A & B & C>
规则:
- 只能有一个类边界(必须是第一个)
- 可以有多个接口边界(用
&连接) - 所有边界必须同时满足
1. 类型参数的多重边界:
java
// T 必须同时满足:
// 1. 是 Comparable<T> 的子类型
// 2. 实现 Serializable 接口
// 3. 实现 Cloneable 接口
public class SortedList<T extends Comparable<T> & Serializable & Cloneable> {
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items); // 需要 Comparable
}
public T cloneAndSort(T item) throws CloneNotSupportedException {
T cloned = (T) item.clone(); // 需要 Cloneable
// 序列化需要 Serializable
return cloned;
}
}
// 使用示例
class Person implements Comparable<Person>, Serializable, Cloneable {
private String name;
@Override
public int compareTo(Person o) {
return name.compareTo(o.name);
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
SortedList<Person> list = new SortedList<>(); // ✅ Person 满足所有边界
2. 多重边界的类型擦除:
java
// 编译前
public class SortedList<T extends Comparable<T> & Serializable & Cloneable> {
private T value;
}
// 类型擦除后
public class SortedList {
private Comparable value; // 擦除为第一个边界类型
// 但运行时仍会检查其他边界
}
3. 多重边界的限制:
java
// ❌ 错误:不能有多个类边界
// public class Test<T extends Number & String> { } // 编译错误
// ✅ 正确:一个类 + 多个接口
public class Test<T extends Number & Serializable & Cloneable> { }
// ❌ 错误:接口不能在类之前
// public class Test<T extends Serializable & Number> { } // 编译错误
// ✅ 正确:类必须在第一位
public class Test<T extends Number & Serializable> { }
4. 多重边界的实际应用:
java
// 应用1:需要排序和序列化的集合
public class SerializableSortedList<T extends Comparable<T> & Serializable> {
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items);
}
public byte[] serialize() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(items);
return baos.toByteArray();
}
}
// 应用2:需要比较和克隆的对象池
public class CloneableObjectPool<T extends Comparable<T> & Cloneable> {
private TreeSet<T> pool = new TreeSet<>();
public T getAndClone() throws CloneNotSupportedException {
T item = pool.first();
return (T) item.clone();
}
}
// 应用3:泛型方法的多重边界
public static <T extends Number & Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// 使用
Integer maxInt = max(10, 20); // ✅ Integer extends Number & Comparable<Integer>
Double maxDouble = max(1.5, 2.5); // ✅ Double extends Number & Comparable<Double>
5. 多重边界与通配符的结合:
java
// 通配符不能直接使用多重边界
// List<? extends Number & Comparable> list; // ❌ 编译错误
// 但可以通过类型参数间接使用
public <T extends Number & Comparable<T>> void process(List<T> list) {
// T 满足多重边界
Collections.sort(list); // 需要 Comparable
Number sum = list.stream()
.mapToDouble(Number::doubleValue)
.sum(); // 需要 Number
}
// 使用
List<Integer> intList = Arrays.asList(1, 2, 3);
process(intList); // ✅ Integer extends Number & Comparable<Integer>
6. 多重边界的类型推断:
java
// 编译器会推断类型参数是否满足多重边界
public static <T extends Comparable<T> & Serializable> void method(T item) {
// ...
}
// 类型推断
method(new Integer(1)); // ✅ 推断 T = Integer
// Integer extends Number, implements Comparable<Integer>, Serializable
// 如果类型不满足所有边界,编译错误
class NotSerializable implements Comparable<NotSerializable> {
// 没有实现 Serializable
}
// method(new NotSerializable()); // ❌ 编译错误
7. 多重边界的性能考虑:
java
// 类型擦除后,所有边界信息丢失
// 但编译器会在编译期检查所有边界
// 运行时只保留第一个边界类型
// 编译前
<T extends Number & Serializable & Cloneable>
// 类型擦除后
// T 擦除为 Number(第一个边界)
// 但方法调用时仍会检查其他边界(通过桥接方法)
通配符边界对比总结
| 特性 | ? extends T |
? super T |
? |
|---|---|---|---|
| 含义 | T 或其子类型 | T 或其父类型 | 任意类型 |
| 读取 | ✅ 可读为 T | ❌ 只能读为 Object | ❌ 只能读为 Object |
| 写入 | ❌ 不能写(除 null) | ✅ 可写 T 及其子类型 | ❌ 不能写(除 null) |
| 用途 | Producer(生产者) | Consumer(消费者) | 只读场景 |
| 类型安全 | 读取安全 | 写入安全 | 最安全但最受限 |
选择指南:
- 只读取 :使用
? extends T - 只写入 :使用
? super T - 既读又写 :使用具体类型参数
<T> - 完全只读 :使用
?
泛型方法详解
java
// 泛型方法:类型参数在方法上
public static <T> T identity(T value) {
return value;
}
// 类型推断
String s = identity("hello"); // 编译器推断 T = String
Integer i = identity(1); // 编译器推断 T = Integer
// 显式指定类型
String s = GenericClass.<String>identity("hello");
// 泛型方法与通配符的区别
public static <T> void method1(List<T> list) {
// T 是具体类型,可以添加 T 类型元素
list.add(list.get(0));
}
public static void method2(List<?> list) {
// ? 是未知类型,不能添加元素(除了 null)
// list.add(list.get(0)); // 编译错误
}
注解(Annotation)
- 元注解 :
@Retention、@Target、@Inherited、@Documented、@Repeatable。 - 生命周期 :
RetentionPolicy.SOURCE(编译期)、CLASS(编译后,默认)、RUNTIME(运行期可反射获取)。 - 注解处理器 :APT(Annotation Processing Tool),可生成代码(
javax.annotation.processing.Processor)。 - 常见注解生态 :
- Lombok:源码级模板。
- Spring:
@Component、@Autowired。 - JSR 380:
@NotBlank、@Email等 Bean Validation。
反射(Reflection)深度解析
核心 API 使用
获取 Class 对象的三种方式:
java
// 1. 类名.class
Class<String> clazz1 = String.class;
// 2. 对象.getClass()
String str = "hello";
Class<?> clazz2 = str.getClass();
// 3. Class.forName()
Class<?> clazz3 = Class.forName("java.lang.String");
动态创建对象:
java
// 旧方式(已过时)
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance(); // 要求无参构造器,已过时
// 新方式(推荐)
Class<?> clazz = Class.forName("com.example.User");
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Object obj = constructor.newInstance("Alice", 25);
访问私有成员:
java
public class User {
private String name;
private void privateMethod() { }
}
// 访问私有字段
User user = new User();
Field field = User.class.getDeclaredField("name");
field.setAccessible(true); // 绕过访问控制
field.set(user, "Bob");
String name = (String) field.get(user);
// 调用私有方法
Method method = User.class.getDeclaredMethod("privateMethod");
method.setAccessible(true);
method.invoke(user);
动态代理深度解析
JDK 动态代理原理:
java
// 1. 定义接口
public interface UserService {
void save(String name);
void delete(Long id);
}
// 2. 实现类
public class UserServiceImpl implements UserService {
@Override
public void save(String name) {
System.out.println("Saving: " + name);
}
@Override
public void delete(Long id) {
System.out.println("Deleting: " + id);
}
}
// 3. 实现 InvocationHandler
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After: " + method.getName());
return result;
}
}
// 4. 创建代理对象
UserService target = new UserServiceImpl();
InvocationHandler handler = new LoggingHandler(target);
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
handler
);
// 5. 使用代理
proxy.save("Alice"); // 会输出日志
JDK 动态代理实现原理(简化):
java
// Proxy.newProxyInstance 内部实现(简化)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 1. 生成代理类的字节码
Class<?> proxyClass = generateProxyClass(loader, interfaces);
// 2. 获取构造器
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
// 3. 创建代理实例
return constructor.newInstance(h);
}
// 生成的代理类(简化,实际由 JVM 动态生成)
public final class $Proxy0 extends Proxy implements UserService {
private static Method m0; // hashCode
private static Method m1; // equals
private static Method m2; // toString
private static Method m3; // save
private static Method m4; // delete
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void save(String name) {
try {
h.invoke(this, m3, new Object[]{name});
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
CGLIB 动态代理:
java
// CGLIB 基于子类,不需要接口
public class UserService {
public void save(String name) {
System.out.println("Saving: " + name);
}
}
// 实现 MethodInterceptor
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("After: " + method.getName());
return result;
}
}
// 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LoggingInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.save("Alice");
JDK 代理 vs CGLIB 代理:
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 基于 | 接口 | 类(子类) |
| 性能 | 较慢(反射调用) | 较快(FastClass) |
| 限制 | 必须有接口 | 不能代理 final 类/方法 |
| 使用场景 | Spring AOP(有接口) | Spring AOP(无接口) |
性能优化:
java
// 1. 缓存 Method 对象
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getDeclaringClass().getName() + "." + method.getName();
Method cached = METHOD_CACHE.get(key);
if (cached == null) {
METHOD_CACHE.put(key, method);
cached = method;
}
return cached.invoke(target, args);
}
// 2. 使用 MethodHandle(JDK 7+)
MethodHandle handle = MethodHandles.lookup()
.findVirtual(target.getClass(), "save", MethodType.methodType(void.class, String.class));
handle.invoke(target, "Alice"); // 比反射快
源码讲解|关键片段
示例 1:泛型仓储接口
java
public interface Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
void save(T entity);
}
java
public class InMemoryRepository<T, ID> implements Repository<T, ID> {
private final Map<ID, T> data = new ConcurrentHashMap<>();
@Override
public Optional<T> findById(ID id) {
return Optional.ofNullable(data.get(id));
}
@Override
public List<T> findAll() {
return new ArrayList<>(data.values());
}
@Override
public void save(T entity) {
ID id = extractId(entity);
data.put(id, entity);
}
@SuppressWarnings("unchecked")
private ID extractId(T entity) {
try {
Method idMethod = entity.getClass().getMethod("getId");
return (ID) idMethod.invoke(entity);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("未找到 getId 方法", e);
}
}
}
面试亮点:泛型接口 + 反射调用结合,说明类型安全与灵活性的权衡。
示例 2:自定义注解 + 运行时代理
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuditLog {
String value();
}
java
public class AuditProxy implements InvocationHandler {
private final Object target;
public AuditProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
AuditLog audit = method.getAnnotation(AuditLog.class);
if (audit != null) {
System.out.printf("操作=%s, 参数=%s%n", audit.value(), Arrays.toString(args));
}
return method.invoke(target, args);
}
public static <T> T createProxy(T target, Class<T> interfaceType) {
return interfaceType.cast(
Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class[]{interfaceType},
new AuditProxy(target)
)
);
}
}
面试亮点:阐述注解 + 反射 + 动态代理组合成 AOP 功能的原理。
实战伪代码
场景:校验框架的最小实现
java
// 定义注解
@Retention(RUNTIME)
@Target(FIELD)
annotation NotBlank { String message() default "不能为空"; }
// 校验器
class Validator {
List<String> validate(Object target):
messages = new ArrayList()
for field in target.class.declaredFields:
if field has NotBlank:
field.setAccessible(true)
value = field.get(target)
if value == null or value.trim().isEmpty():
messages.add(field.name + ":" + annotation.message())
return messages
}
// 使用
class UserForm {
@NotBlank(message="用户名必填")
String username;
}
关键点:遍历字段、读取注解、通过反射取值并给出错误信息。
应用场景
- ORM 框架:JPA/Hibernate 使用注解声明实体映射,通过反射生成 SQL。
- 依赖注入 :Spring
@Autowired解析,结合ReflectionUtils注入字段。 - 序列化框架:Jackson/Gson 使用泛型确保类型安全,同步利用注解描述字段属性。
- 插件化架构:SPI(Service Provider Interface) + 反射加载实现,支持运行时扩展。
- 编译期代码生成:MapStruct、Lombok 借助 APT 自动生成样板代码。
高频面试问答(深度解析)
1. Java 泛型是如何实现的?类型擦除的原理是什么?
标准答案:
- Java 泛型通过类型擦除实现,编译期检查类型,运行期擦除类型信息
- 类型参数被擦除为边界类型(有界)或
Object(无界) - 编译器自动插入类型转换和桥接方法
深入追问与回答思路:
Q: 为什么 Java 选择类型擦除而不是真泛型(如 C#)?
- 向后兼容:保持与旧版本 Java 的兼容性
- JVM 层面:不需要修改 JVM,只需修改编译器
- 性能:运行时没有类型信息,性能开销小
Q: 类型擦除后如何保证类型安全?
java
// 编译期检查
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(1); // 编译错误
// 编译后(类型擦除)
List list = new ArrayList();
list.add("hello");
// 编译器自动插入类型转换
String s = (String) list.get(0);
Q: 如何获取泛型的实际类型?
java
// 方法1:通过子类继承
public abstract class GenericType<T> {
private final Type type;
protected GenericType() {
Type superClass = getClass().getGenericSuperclass();
type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
// 使用
class StringType extends GenericType<String> { }
StringType stringType = new StringType();
Type type = stringType.getType(); // String.class
// 方法2:通过方法参数
public <T> void method(List<T> list) {
Type type = ((ParameterizedType)
getClass().getDeclaredMethods()[0]
.getGenericParameterTypes()[0])
.getActualTypeArguments()[0];
}
2. 为什么 List<?> 不能添加元素(除了 null)?
标准答案:
- 通配符
?表示未知类型,编译器无法确定具体类型 - 为了保证类型安全,禁止添加元素(除了
null,因为null是所有类型的子类型) - 可以读取,但读取的类型是
Object
深入追问与回答思路:
Q: List<?> 和 List<Object> 的区别?
java
// List<?>:未知类型,更灵活
List<?> wildcard = new ArrayList<String>();
wildcard = new ArrayList<Integer>(); // 可以赋值
// List<Object>:明确是 Object 类型
List<Object> object = new ArrayList<String>(); // 编译错误
Q: PECS 原则的实际应用?
java
// Producer Extends:只读取
public void printNumbers(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n);
}
// numbers.add(1); // 编译错误
}
// Consumer Super:只写入
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
// Integer i = list.get(0); // 编译错误,只能读取为 Object
}
3. @Retention 与 @Target 有何作用?
标准答案:
- @Retention:控制注解的生命周期(SOURCE、CLASS、RUNTIME)
- @Target:控制注解可以使用的位置(类、方法、字段等)
深入追问与回答思路:
Q: 三种 Retention 的区别?
java
// SOURCE:编译期,编译后丢弃(如 @Override)
@Retention(RetentionPolicy.SOURCE)
public @interface Override { }
// CLASS:编译后保留,但运行期不可访问(默认)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation { }
// RUNTIME:运行期可访问,可通过反射获取
@Retention(RetentionPolicy.RUNTIME)
public @interface Component { }
Q: 如何自定义注解处理器?
java
// 1. 定义注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Builder {
}
// 2. 实现处理器
@SupportedAnnotationTypes("com.example.Builder")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Builder.class)) {
// 生成代码
generateBuilderClass(element);
}
return true;
}
}
4. JDK 动态代理与 CGLIB 的区别?
标准答案:
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 基于 | 接口 | 类(子类) |
| 性能 | 较慢(反射) | 较快(FastClass) |
| 限制 | 必须有接口 | 不能代理 final 类/方法 |
| 字节码 | 运行时生成 | 运行时生成 |
深入追问与回答思路:
Q: Spring AOP 如何选择代理方式?
java
// Spring 的代理策略
if (targetClass.getInterfaces().length > 0) {
// 有接口:使用 JDK 动态代理
return Proxy.newProxyInstance(...);
} else {
// 无接口:使用 CGLIB
return enhancer.create();
}
Q: 如何提升代理性能?
- 缓存代理类:避免重复生成
- 使用 MethodHandle:比反射快
- CGLIB FastClass:直接调用,避免反射
5. 反射为何性能较慢?如何优化?
标准答案:
- 访问检查:每次调用都检查访问权限
- 方法查找:需要在方法表中查找
- 类型转换:需要类型检查和转换
- 无法内联:JIT 编译器难以优化
深入追问与回答思路:
Q: 性能对比?
java
// 直接调用:最快
user.getName();
// 反射调用:慢 10-100 倍
Method method = User.class.getMethod("getName");
method.invoke(user);
// MethodHandle:比反射快,但仍慢于直接调用
MethodHandle handle = MethodHandles.lookup()
.findVirtual(User.class, "getName", MethodType.methodType(String.class));
handle.invoke(user);
Q: 如何优化反射性能?
java
// 1. 缓存 Method 对象
private static final Method GET_NAME_METHOD =
User.class.getMethod("getName");
// 2. 使用 MethodHandle
private static final MethodHandle GET_NAME_HANDLE =
MethodHandles.lookup()
.findVirtual(User.class, "getName", MethodType.methodType(String.class));
// 3. 使用字节码生成(如 CGLIB、ASM)
// 4. 使用注解处理器在编译期生成代码
6. 注解处理器如何工作?
标准答案:
- 编译期扫描注解
AbstractProcessor处理注解- 生成新源文件或修改现有文件
深入追问与回答思路:
Q: 注解处理器的执行时机?
1. 解析源代码
2. 执行注解处理器(可能生成新文件)
3. 如果生成了新文件,重新解析
4. 重复直到没有新文件生成
5. 编译所有源文件
Q: 实际应用案例?
- Lombok:生成 getter/setter、构造器等
- MapStruct:生成对象映射代码
- Dagger:生成依赖注入代码
- ButterKnife:生成视图绑定代码
延伸阅读
- 官方教程:Java Generics FAQ、Reflection Trail、
java.lang.reflectAPI - 《Effective Java》第 3 版:条款 26-41(泛型)与 AOP 实践章节
- Spring 源码:
AnnotationUtils、ProxyFactory - 工具库:Guava
TypeToken、MapStruct、AutoService(注解处理器注册)