01.04 Java基础篇|泛型、注解与反射实战

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);  // 读取时强制转换

类型擦除的规则

  1. 无界类型参数<T>Object
  2. 有界类型参数<T extends Number>Number
  3. 通配符<?>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. 只能有一个类边界(必须是第一个)
  2. 可以有多个接口边界(用 & 连接)
  3. 所有边界必须同时满足

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.reflect API
  • 《Effective Java》第 3 版:条款 26-41(泛型)与 AOP 实践章节
  • Spring 源码:AnnotationUtilsProxyFactory
  • 工具库:Guava TypeToken、MapStruct、AutoService(注解处理器注册)
相关推荐
大猫和小黄1 小时前
Windows环境下在VMware中安装和配置CentOS 7
linux·windows·centos
DechinPhy1 小时前
使用Python免费合并PDF文件
开发语言·数据库·python·mysql·pdf
深圳佛手2 小时前
Java大对象(如 List、Map)如何复用?错误的方法是?正确的方法是?
java·jvm·windows
qq_252614412 小时前
python爬虫爬取视频
开发语言·爬虫·python
言之。2 小时前
Claude Code Skills 实用使用手册
java·开发语言
苹果醋32 小时前
JAVA设计模式之策略模式
java·运维·spring boot·mysql·nginx
千寻技术帮2 小时前
10370_基于Springboot的校园志愿者管理系统
java·spring boot·后端·毕业设计
Rinai_R2 小时前
关于 Go 的内存管理这档事
java·开发语言·golang
咸鱼加辣2 小时前
【python面试】你x的启动?
开发语言·python