泛型与类型擦除与注解与反射

共 66 题
📑 题目列表 66 题
#
★★★

1. Class.cast 与普通强制类型转换相比提供什么动态类型保证,Class.asSubclass 又解决什么问题

Class.cast 与普通强制类型转换相比提供什么动态类型保证?Class.asSubclass 解决什么问题?

  • Class.cast 动态类型检查
  • 普通强转的编译期检查
  • asSubclass 的用途

Class.cast(obj) 是动态类型检查:在运行时检查 obj 是否是 Class 代表的类型,若是则返回,否则抛 ClassCastException。它比普通强转的"编译期检查"多一层运行时保证(普通强转在编译期无法确定类型时也可运行,但错误在运行时抛 CCE)。Class.cast 常用于"运行时才知道类型"的场景(如泛型擦除后从 Object 恢复类型)。Class.asSubclass(Class) 检查当前类是否是给定类的子类,若是返回子类 Class,否则抛 ClassCastException。它解决"运行时验证类型层次关系"并将 Class 类型收窄为子类 Class 的问题,常用于反射中确保类型兼容。

cast 做运行时类型检查返回实例,asSubclass 做运行时类型层次检查并收窄 Class 类型。二者都是运行时动态类型保证。

Class<?> cls = ...;
// 动态类型检查
Object obj = ...;
String s = cls.cast(obj);       // 若 obj 不是该类型抛 CCE
// 运行时类型层次验证
Class<? extends Number> nc = Number.class.asSubclass(Integer.class);  // 会抛 CCE
#
★★★

2. VarHandle 在原子操作与字段访问的应用

VarHandle 在原子操作与字段访问的应用是什么?

  • VarHandle 与原子操作
  • 字段访问
  • 与 Atomic 类/Unsafe 的关系

VarHandle(JDK 9+)提供对对象的字段、数组元素、static 字段的原子操作与内存访问控制,是 Variable Handles API。应用:1)原子操作:compareAndSet、getAndSet、getAndAdd、getAndUpdate 等,对字段做 CAS 原子更新,替代手写 synchronized 或 AtomicReferenceFieldUpdater;2)字段访问:get/set 直接访问字段,支持 volatile 语义与内存排序(memory order);3)替代 sun.misc.Unsafe 的部分功能(更安全、规范)。VarHandle 通过 MethodHandles.lookup() 获取,绑定到字段,提供类型安全、高效(JIT 优化)的原子字段访问。相比 Atomic 类,VarHandle 可对任意字段(非仅 Atomic* 类型)做原子操作。

VarHandle 提供字段/数组的原子操作与内存访问控制,类型安全、性能好,替代 Unsafe 与手动同步。

class Counter {
    int value;
    static final VarHandle VH = MethodHandles.lookup().findVarHandle(Counter.class, "value", int.class);
    int increment() {
        return (int) VH.getAndAdd(this, 1);   // 原子自增
    }
    boolean cas(int expect, int update) {
        return VH.compareAndSet(this, expect, update);
    }
}
#
★★★

3. 从原始类型 API 迁移到参数化 API 时,怎样缩小 unchecked 警告范围并发现潜在 ClassCastException

从原始类型 API 迁移到参数化 API 时,怎样缩小 unchecked 警告范围并发现潜在 ClassCastException?

  • raw type 迁移
  • unchecked 警告
  • 发现 ClassCastException

从原始类型(raw type)迁移到参数化 API 时,编译器会报 unchecked 警告(类型擦除导致运行时无法检查)。缩小警告范围:1)逐步、按模块迁移(先改方法签名,再改实现),控制警告范围;2)用 @SuppressWarnings("unchecked") 只标注确实无法避免的局部(不放大到整个方法/类);3)用 -Werror 或 -Xlint:unchecked 让警告可见,逐个处理;4)迁移后运行测试,传入错误类型会在运行时抛 ClassCastException,结合日志定位。发现潜在 CCE:运行期用测试覆盖(JUnit)、在注入点做类型检查(Objects.requireNonNull + cast)、用泛型 API 后编译器保证类型一致。目标是消除 raw type,让编译器在编译期捕捉类型错误。

迁移通过"逐个处理 unchecked 警告 + 局部 @SuppressWarnings + 测试覆盖"来缩小范围并发现 CCE,最终让编译器保证类型安全。

// 迁移前:raw type
List list = new ArrayList();
list.add("a");
// 迁移后:参数化
List<String> list = new ArrayList<>();
list.add("a");
// 局部抑制 unchecked(无法避免处)
@SuppressWarnings("unchecked")
List<String> cast = (List<String>) rawList;
#
★★★

4. 方法引用遇到重载与泛型方法时,编译器如何根据函数式接口目标签名选择候选方法

方法引用遇到重载与泛型方法时,编译器如何根据函数式接口目标签名选择候选方法?

  • 方法引用与目标类型
  • 重载解析
  • 泛型方法选择

方法引用(如 String::valueOf、List::add)编译时根据"目标函数式接口的签名"(参数类型、返回类型)选择候选方法。当方法引用遇到重载(多个同名方法)或泛型方法时,编译器做"重载解析":1)先确定目标函数式接口的抽象方法签名(参数、返回);2)从候选方法(重载)中筛选签名兼容者(参数个数/类型匹配、返回类型兼容);3)若匹配多个则按"最具体"规则选择,或泛型方法按类型推断实例化。编译器结合"目标类型"(target typing)与"参数类型"推断候选方法。若无法唯一确定会编译错误。例:String::valueOf 匹配多种重载(valueOf(int)、valueOf(Object) 等),根据目标函数式接口选择。

方法引用按"目标函数式接口签名"做重载解析与泛型推断,选择签名兼容且最具体的候选方法。

// String::valueOf 有多个重载,按目标类型选择
Function<Integer, String> f1 = String::valueOf;   // 选 valueOf(int)
Function<Object, String> f2 = String::valueOf;   // 选 valueOf(Object)
// 泛型方法:List::add 是泛型,按目标类型推断
BiConsumer<List<String>, String> add = List::add;
#
★★★

5. 模式匹配 instanceof 对泛型类型变量有哪些限制,成功匹配后编译器能保留哪些类型信息

模式匹配 instanceof 对泛型类型变量有哪些限制?成功匹配后编译器能保留哪些类型信息?

  • instanceof 泛型限制
  • 模式变量类型
  • 类型擦除

instanceof 模式匹配对泛型类型有限制:由于类型擦除,obj instanceof List<String> 无法检查泛型参数(运行时无法区分 List 与 List),只能检查 instanceof List(原始类型)或 instanceof List<?>(通配符);泛型类型变量/参数化类型不能作为 instanceof 模式(编译错误)。成功匹配后,编译器能保留的模式变量类型是"可具体化类型":如 instanceof List<?> l 匹配后 l 的类型是 List<?>(可具体化),但无法保留到 String 元素级。若模式变量是具体类型(如 String s)则保留完整类型。泛型参数在运行时被擦除,无法通过 instanceof 保留。

泛型擦除使 instanceof 只能匹配可具体化类型(List<?>),不能匹配 List。模式变量类型受擦除限制。

if (obj instanceof List<?> l) {   // 可匹配(通配符)
    // l 类型为 List<?>,无法知道元素类型
}
// if (obj instanceof List<String> s) {}  // 编译错误:不可具体化
if (obj instanceof String s) {   // 可具体化,保留 String 类型
}
#
★★★

6. 通配符捕获辅助方法如何修改 List<?> 中的元素,为什么直接写入非 null 值无法通过编译

通配符捕获辅助方法如何修改 List<?> 中的元素?为什么直接写入非 null 值无法通过编译?

  • 通配符捕获
  • List<?> 的写入限制
  • 辅助方法

List 表示"未知类型的元素列表",直接写非 null 值无法通过编译,因为编译器不知道元素类型,无法保证写入的对象类型匹配(除了 null,null 是任何类型的子类型)。通配符捕获(wildcard capture):通过辅助方法把 List 的未知类型捕获为类型变量 T,即 void helper(List<?> list) { helper2(list); } 其中 private <T> void helper2(List<T> list) 内部可以用 T 处理元素(如用 T 类型变量、list.get 返回 T)。这样在辅助方法内部,类型被"捕获"为具体类型变量,可以进行安全的类型操作(如交换、获取),但也不能放入不匹配的任意值。通配符捕获让编译器在辅助方法内把 ? 当成具体类型处理。

List<?> 不能写非 null(类型未知),辅助方法用 捕获通配符为类型变量,编译器在方法内按 T 处理,实现安全操作。

void swap(List<?> list, int i, int j) {
    helper(list, i, j);   // 通配符捕获
}
private <T> void helper(List<T> list, int i, int j) {
    T tmp = list.get(i);   // T 类型
    list.set(i, list.get(j));
    list.set(j, tmp);
}
// list.add("x") 对 List<?> 编译失败(非 null 无法确认类型)
#
★★★

7. @Repeatable 注解的容器类型(Container Annotation)合成机制

@Repeatable 注解的容器类型(Container Annotation)合成机制是什么?

  • @Repeatable 元注解
  • 容器注解
  • 合成获取

@Repeatable 允许同一注解在目标上重复使用。实现机制:需定义"容器注解"(Container Annotation),它包含一个数组类型成员(元素类型是被重复的注解),并在被重复注解上标注 @Repeatable(Container.class)。例如定义 @Repeatable(Tags.class),其中 @interface Tags { Tag[] value(); }。用 @Tag("a") @Tag("b") 时,编译器自动合成一个 @Tags 容器注解,把多个 @Tag 装进 value 数组。读取时,getAnnotationsByType(Tag.class) 会同时返回容器内的单个注解(编译器解析容器),或 getAnnotation(Tags.class) 获取容器。这是编译期合成机制,让注解可重复并统一读取。

@Repeatable 通过容器注解(含数组成员)合成多个重复注解,编译器把重复注解包装进容器,getAnnotationsByType 统一读取。

@Repeatable(Tags.class)
@interface Tag { String value(); }
@interface Tags { Tag[] value(); }   // 容器注解

@Tag("a") @Tag("b")
class Foo {}
// 读取:Foo.class.getAnnotationsByType(Tag.class) -> [@Tag("a"), @Tag("b")]
#
★★★

8. @SafeVarargs 的合法场景

@SafeVarargs 的合法场景是什么?

  • @SafeVarargs 用途
  • 合法场景
  • 抑制 unchecked 警告

@SafeVarargs 用于标注"不会对 varargs 参数做不安全操作"的方法,抑制"堆污染"(heap pollution)的 unchecked 警告。合法场景:1)方法必须是 static、final 或 private(JDK 9 起);2)方法不能覆盖(无法被覆写),保证 varargs 参数不被外部以不安全方式使用;3)方法体内不把 varargs 数组暴露给外部(不存储、不返回、不把非 T 元素放入数组)。合法场景是"方法只读地使用 varargs,不把数组泄漏给外部、不进行类型不安全操作"。JDK 9 起允许 private 方法。用于标注"安全使用 varargs"的方法,避免误报警告。

@SafeVarargs 合法用于 static/final/private 且安全使用 varargs 的方法(不暴露数组、不不安全操作),抑制堆污染警告。

@SafeVarargs
static <T> List<T> asList(T... args) {
    return new ArrayList<>(Arrays.asList(args));   // 安全使用 varargs
}
// 非法:@SafeVarargs 用于可变方法(可被覆写)编译错误
#
★★★

9. @SuppressWarnings("unchecked") 的正确范围

@SuppressWarnings("unchecked") 的正确范围是什么?

  • @SuppressWarnings 范围
  • 最小化抑制
  • 抑制 unchecked 警告

@SuppressWarnings("unchecked") 应尽量缩小范围,只标注真正需要抑制的"最小局部"(局部变量声明、方法、字段),而不是整个类或整个方法。因为范围过大:1)掩盖该方法/类中其他真正的 unchecked 警告;2)未来新增代码也可能被抑制,隐藏类型问题。正确范围:能标到局部变量就标局部变量,其次方法,最后才类。同时应配合注释说明为什么无法避免(如"与 legacy 代码交互")。目标是让编译器对"确实无法避免的 unchecked 操作"保持安静,同时不掩盖其他警告。

@SuppressWarnings 应最小化范围(局部声明/方法/类),避免掩盖其他 unchecked 警告,配合注释说明抑制理由。

// 反模式:整个类抑制
@SuppressWarnings("unchecked")
class Foo { ... }
// 正确:局部抑制
@SuppressWarnings("unchecked")
List<String> list = (List<String>) rawList;   // 只抑制这一处
#
★★★

10. AccessibleObject.setAccessible(true) 在 JDK 17 强封装下的合法性边界

AccessibleObject.setAccessible(true) 在 JDK 17 强封装下的合法性边界是什么?

  • 强封装(JDK 17+)
  • setAccessible 的合法性
  • --add-opens

在 JDK 17+ 强封装(JPMS 强封装)下,setAccessible(true) 不再"无条件"合法:它只有在满足条件时才能访问私有成员。边界:1)对"自己模块内"的类,setAccessible(true) 合法;2)对"其他模块"的类,若该模块未对调用模块 opens(--add-opens 或模块描述符 opens),setAccessible(true) 抛出 InaccessibleObjectException/IllegalAccessException;3)JDK 内部(java.base)的私有成员默认不可反射访问,需 --add-opens java.base/java.lang=ALL-UNNAMED 等。JDK 17 起默认强封装,反射框架(Spring/Hibernate 等)需要 --add-opens 清单才能访问 JDK 内部。合法性边界:模块是否 opens 决定 setAccessible 是否有效。

JDK 17 强封装下,setAccessible 需模块 opens 才合法,否则抛异常。反射框架需配置 --add-opens。

// 访问 JDK 内部(java.base)需 --add-opens
// java --add-opens java.base/java.lang=ALL-UNNAMED -cp app.jar Main
Field f = SomeClass.class.getDeclaredField("x");
f.setAccessible(true);   // 若模块未 opens,抛 InaccessibleObjectException
#
★★★

11. Annotation 的合成与继承机制

Annotation 的合成与继承机制是什么?

  • @Inherited 注解继承
  • 合成注解(@Repeatable 容器)
  • 注解读取

Annotation 的合成与继承机制:1)继承:@Inherited 元注解标注的注解可被子类继承(isAnnotationPresent 在子类上能查到父类的该注解),但非 @Inherited 注解不继承(仅类上);接口注解不继承给实现类;方法注解不继承;2)合成:@Repeatable 的容器注解是编译器合成的,某些场景 JVM 会生成合成注解(synthetic annotations);3)读取:getAnnotation 直接查,getAnnotations 返回所有,getAnnotationsByType 处理重复注解。继承机制:@Inherited 只影响"类注解"的继承查询,不改变字段/方法注解。合成机制让重复注解可被统一读取。

@Inherited 让类注解可被继承查询,@Repeatable 合成为容器注解。理解继承与合成才能正确读取注解。

@Inherited @Retention(RUNTIME) @interface SuperAnn {}
@SuperAnn class Base {}
class Child extends Base {}   // Child 继承 @SuperAnn
Child.class.isAnnotationPresent(SuperAnn.class);  // true
// 非 @Inherited 注解不继承
#
★★★

12. CGLIB 与 ByteBuddy 字节码生成原理对比

CGLIB 与 ByteBuddy 字节码生成原理如何对比?

  • CGLIB 字节码生成
  • ByteBuddy 字节码生成
  • 对比

CGLIB 与 ByteBuddy 都是字节码生成工具,用于在运行时生成/修改类(如代理、AOP、DTO 增强)。CGLIB:经典字节码库,用 ASM 生成子类(继承目标类生成代理子类),实现接口代理(Enhancer),用于 Spring AOP(目标类非接口时)。ByteBuddy:更现代、更易用的字节码库,基于 ASM 但提供高层 API(ClassBuilder、Method/Field DSL),支持动态类创建、方法拦截、注解、泛型等,性能好、API 友好,被 Mockito、Hibernate 等现代框架使用。对比:CGLIB 较老、API 较低层(直接 ASM),ByteBuddy 更现代、API 更友好、能力更强(生成任意类、修改方法)。两者都生成字节码,ByteBuddy 逐渐取代 CGLIB。

两者都用 ASM 生成字节码,CGLIB 较老低层,ByteBuddy 现代高层 API 更强大友好,被现代框架采用。

// CGLIB:Enhancer 生成子类代理
Enhancer e = new Enhancer(); e.setSuperclass(Service.class); e.setCallback(...);
// ByteBuddy:高层 DSL
new ByteBuddy().subclass(Service.class)
    .method(any()).intercept(...)
    .make().load(...);
#
★★★

13. CGLIB 通过 FastClass 机制绕过反射调用的性能优势与 final 方法/类的限制

CGLIB 通过 FastClass 机制绕过反射调用的性能优势是什么?final 方法/类有何限制?

  • FastClass 机制
  • 绕过反射的性能
  • final 限制

CGLIB 的 FastClass 机制:为每个类生成一个 FastClass 类,通过索引(int)直接调用方法("index + 参数"快速分发),避免使用反射(Method.invoke),减少反射开销,提升代理方法调用性能。FastClass 生成一个方法索引表,用整数索引定位方法,参数类型校验后直接调用,比反射快。限制:final 方法与 final 类不能被子类代理——CGLIB 通过继承目标类生成子类,final 方法无法覆写、final 类无法被继承,因此无法代理 final 方法/类(Spring AOP 对 final 类/方法会告知无法代理)。这是 CGLIB 子类代理的固有边界。

FastClass 用索引直接调用方法绕过反射提性能;但 CGLIB 靠继承子类,final 方法/类无法代理。

// FastClass:生成索引表,method(index, args) 直接调用
// 避免 Method.invoke 反射开销
// final 限制:final 类/方法无法被继承/覆写,CGLIB 无法代理
public final class FinalService {}   // 无法 CGLIB 代理
#
★★★

14. Class<? extends X> 的反射 API 限制

Class<? extends X> 的反射 API 有哪些限制?

  • 有界通配符 Class
  • 反射 API 限制
  • 类型安全

Class<? extends X> 表示"X 或其子类的 Class 对象"。反射 API 限制:虽然类型上界是 X,但通过该 Class 能获取的信息仍受擦除限制——例如 getGenericSuperclass、getMethod 返回的泛型信息可能丢失具体参数;调用 newInstance 需要该类有无参构造器;用 Class<? extends X> 做类型转换时,编译器只能保证"是 X 的子类",无法进一步确定具体类型。实际限制:Class<? extends X> 无法直接用于需要具体 Class 的反射 API(如需要 Class 精确匹配),且泛型信息在擦除后受限。它主要用于"接收某个类的任意子类 Class"的场景,提供类型上界约束。

Class<? extends X> 提供类型上界,但反射 API 受擦除限制,泛型信息可能丢失,newInstance 需无参构造等。

Class<? extends Number> cls = Integer.class;
// 反射:只能按 Number 处理,无法知道具体 Integer
Number n = cls.getDeclaredConstructor().newInstance();
// getGenericSuperclass 返回的泛型信息受擦除限制
#
★★★

15. Class<?>.getDeclaredFields() 在模块化(JPMS)下跨模块反射访问的限制与 opens 指令

Class<?>.getDeclaredFields() 在模块化(JPMS)下跨模块反射访问的限制与 opens 指令是什么?

  • getDeclaredFields 反射
  • JPMS 强封装
  • opens 指令

getDeclaredFields() 返回类声明的所有字段(含私有),但 JPMS 强封装下,跨模块访问私有/非导出成员受限:只能访问"导出且未强封装"的字段,或模块声明 opens 的包内的字段。限制:1)若目标类所在模块未对调用模块 opens 相关包,getDeclaredFields() 能获取字段列表,但 setAccessible(true) 或访问字段值会抛 InaccessibleObjectException/IllegalStateException;2)JDK 内部模块(java.base)默认强封装,需 --add-opens java.base/java.lang=... 才能反射访问其私有成员。opens 指令:模块描述符中 opens 包 to 模块 开放包给指定模块反射访问。反射框架需维护 opens 清单。

JPMS 强封装限制跨模块反射,getDeclaredFields 能列字段但访问私有需模块 opens(--add-opens 或 opens 指令)。

// 模块描述符:opens 包给反射
module app {
    opens com.example.model to spring.core;   // 开放包给反射
}
// 或 JVM 参数:--add-opens com.example.model=ALL-UNNAMED
Field f = SomeClass.class.getDeclaredField("x");
f.setAccessible(true);   // 需 opens 否则抛异常
#
★★★

16. Class 与 Type 的层次结构

Class 与 Type 的层次结构是什么?

  • Class 与 Type 接口
  • Type 子接口
  • 泛型类型表示

Type 是 Java 中"类型"的通用接口,Class 实现了 Type。Type 的层次结构:Type 是根接口,其子接口包括:1)Class(运行时类型,具体化);2)ParameterizedType(参数化类型,如 List );3)TypeVariable(类型变量,如 T);4)WildcardType(通配符,如 ? extends T);5)GenericArrayType(泛型数组,如 T[])。Class 是具体化的运行时类型(无泛型参数),而其他 Type 子接口用于描述"泛型类型"(擦除后仍可通过反射获取)。反射中 getGenericType 返回 Type,getDeclaredField 的可参数化类型等。理解 Type 层次才能解析泛型反射。

Class 是 Type 的具体实现,Type 还有 ParameterizedType/TypeVariable/WildcardType/GenericArrayType 描述泛型类型。

// Type 层次
Type t = field.getGenericType();   // 可能是 ParameterizedType(List<String>)
if (t instanceof ParameterizedType pt) {
    Type[] args = pt.getActualTypeArguments();   // 实际类型参数
}
// Class<T> 是具体化类型,无泛型参数
#
★★★

17. Class.getGenericSuperclass 与 ParameterizedType 在反射获取泛型实参的细节

Class.getGenericSuperclass 与 ParameterizedType 在反射获取泛型实参的细节是什么?

  • getGenericSuperclass
  • ParameterizedType
  • 获取泛型实参

Class.getGenericSuperclass() 返回父类的 Type(通常是 ParameterizedType,若父类带泛型参数)。当父类是泛型类(如 class Foo extends Base),getGenericSuperclass() 返回 ParameterizedType,其 getActualTypeArguments() 返回泛型实参(String)。这是反射获取泛型实参的关键(如实现 TypeToken 模式)。细节:1)getGenericSuperclass 返回 Type,需 instanceof ParameterizedType 判断;2)getActualTypeArguments() 返回 Type[],可能含嵌套泛型(TypeVariable 等);3)若父类无泛型参数,返回 Class。典型应用:泛型 DAO 基类通过 getGenericSuperclass 获取具体泛型参数(如实体类型)。

getGenericSuperclass 返回 ParameterizedType(父类带泛型时),getActualTypeArguments 获取泛型实参,是反射获取泛型信息的常用方式。

class BaseDao<T> {}
class UserDao extends BaseDao<User> {}
Type t = UserDao.class.getGenericSuperclass();   // BaseDao<User>
ParameterizedType pt = (ParameterizedType) t;
Type arg = pt.getActualTypeArguments()[0];       // User
#
★★★

18. JDK 21 模式匹配与记录类结合的解构模式(Deconstruction Pattern)

JDK 21 模式匹配与记录类结合的解构模式(Deconstruction Pattern)是什么?

  • 记录类型模式
  • 解构记录
  • switch 模式匹配

JDK 21 的解构模式(Deconstruction Pattern,record pattern)允许在模式匹配中解构记录类,把记录组件绑定到模式变量。如 case Point(int x, int y) -> ... 匹配 Point 并解构出 x、y。可用于 instanceof 模式匹配(if (obj instanceof Point(int x, int y)))和 switch 模式匹配。解构模式结合嵌套:case Line(Point(int x1, int y1), Point p2) -> ... 可嵌套解构。这使模式匹配支持对记录类的结构性匹配,是"代数数据类型"风格的关键——用模式变量解构记录组件,配合穷尽性检查。记录类天然适合解构(组件固定)。

record pattern 解构记录组件为模式变量,支持 instanceof 与 switch,可嵌套解构,是 JDK 21 模式匹配的核心能力。

record Point(int x, int y) {}
record Line(Point a, Point b) {}
switch (obj) {
    case Point(int x, int y) -> System.out.println(x + "," + y);
    case Line(Point(int x1, int y1), Point b) -> ...;   // 嵌套解构
    default -> {}
}
#
★★★

19. JDK 8 元注解(@Repeatable、@Target 扩展)的应用

JDK 8 元注解(@Repeatable、@Target 扩展)的应用是什么?

  • @Repeatable 重复注解
  • @Target 扩展(TYPE_USE/TYPE_PARAMETER)
  • 类型注解

JDK 8 引入两个元注解增强:1)@Repeatable:允许同一注解在目标上重复使用(需容器注解),用于支持重复注解(如多个 @Author);2)@Target 扩展:新增 ElementType.TYPE_USE(类型注解,作用于类型任意位置)和 ElementType.TYPE_PARAMETER(类型参数注解,作用于泛型类型参数 )。@Target 扩展支持类型注解(可空性检查、Checker Framework 等)与类型参数注解。应用:@Repeatable 用于可重复标注(Web 配置、权限),TYPE_USE 用于类型注解(@NonNull List),TYPE_PARAMETER 用于泛型参数注解。这些是 JDK 8 注解机制的重要增强。

JDK 8 的 @Repeatable 支持重复注解,@Target 新增 TYPE_USE/TYPE_PARAMETER 支持类型注解与类型参数注解。

@Target(ElementType.TYPE_USE) @interface NonNull {}
List<@NonNull String> list;      // 类型注解
@Target(ElementType.TYPE_PARAMETER) @interface Param {}
class C<@Param T> {}             // 类型参数注解
@Repeatable(Anns.class) @interface Ann { String value(); }
#
★★★

20. JDK 动态代理(Proxy/InvocationHandler)的实现原理与只能代理接口的原因(生成 $Proxy0 继承 Proxy)

JDK 动态代理(Proxy/InvocationHandler)的实现原理是什么?为什么只能代理接口?

  • Proxy 与 InvocationHandler
  • 生成 $Proxy0 继承 Proxy
  • 只能代理接口的原因

JDK 动态代理:运行时生成一个代理类(如 $Proxy0),它实现被代理的接口(实现 InvocationHandler),方法调用转发到 InvocationHandler.invoke。生成类继承 Proxy 类(代理类必须继承 Proxy),并实现传入的接口。只能代理接口的原因:代理类继承了 Proxy(Java 单继承),无法再继承目标类(类代理需继承目标类,但已被 Proxy 占用继承位),所以只能通过"实现接口"来代理(接口可多实现)。若目标类无接口,JDK 代理无法使用,需 CGLIB(继承子类)。原理:Proxy.newProxyInstance 生成 $Proxy0 实现接口+继承 Proxy,invoke 转发。

JDK 代理生成 $Proxy0 继承 Proxy 并实现接口,方法调用转发到 InvocationHandler。因继承 Proxy 占用单继承位,只能代理接口,类代理需 CGLIB。

@FunctionalInterface
interface Service { void run(); }
Service s = (Service) Proxy.newProxyInstance(
    cl, new Class[]{Service.class},
    (proxy, method, args) -> { System.out.println("before"); return method.invoke(target, args); });
// $Proxy0 extends Proxy implements Service
#
★★

21. Jackson 或 Spring 中传递 ParameterizedTypeReference 时,如何跨越擦除保留响应体的嵌套泛型

Jackson 或 Spring 中传递 ParameterizedTypeReference 时,如何跨越擦除保留响应体的嵌套泛型?

  • ParameterizedTypeReference
  • 泛型擦除
  • 保留嵌套泛型

ParameterizedTypeReference 是 Spring 提供的类,用于在运行时保留泛型信息(解决擦除):它通过 getGenericSuperclass() 捕获泛型实参(如 new ParameterizedTypeReference<List<Map<String, Integer>>>() {} 创建匿名子类,子类超类泛型保留 List<Map<String,Integer>>)。Jackson 的 TypeReference 类似(重写 getType() 返回实际泛型)。传递时,框架用 getType()/getGenericSuperclass 获取 Type,反序列化时按完整泛型(含嵌套)构造,避免擦除丢失 List 等。做法:匿名子类的方式让编译器在字节码中保留泛型签名,class 文件 signature 属性记录,反射获取。

ParameterizedTypeReference 用匿名子类捕获泛型实参(getGenericSuperclass 读取 signature),让反序列化按完整泛型处理,跨越擦除。

ParameterizedTypeReference<List<Map<String, Integer>>> type =
    new ParameterizedTypeReference<>() {};
// 内部:getGenericSuperclass() 读取 List<Map<String,Integer>> 的 Type
ResponseEntity<List<Map<String, Integer>>> resp =
    restTemplate.exchange(url, GET, null, type);
#
★★

22. Java 17 模式匹配 instanceof 后类型擦除的影响

Java 17 模式匹配 instanceof 后类型擦除的影响是什么?

  • instanceof 模式匹配
  • 类型擦除
  • 泛型限制

Java 17 的 instanceof 模式匹配(JEP 394 定稿)后,类型擦除的影响:模式变量的类型可以是具体化类型(非泛型参数化),但不能是含泛型参数的不可具体化类型(如 List)。擦除导致 instanceof 无法检查泛型参数,因此 instanceof List<String> 非法,只能 instanceof Listinstanceof List<?>。模式变量类型在擦除后保留的是"可具体化类型"。若模式变量是泛型参数化类型,编译器报错(不可具体化)。对 instanceof 模式匹配,擦除限制了可匹配的泛型类型,但可安全匹配具体类型与通配符泛型。

instanceof 模式匹配受擦除影响,只能匹配可具体化类型,List 非法,List<?> 可匹配,模式变量保留可具体化类型。

if (obj instanceof List<?> l) { ... }   // 可具体化,可匹配
// if (obj instanceof List<String> s) {}  // 编译错误:不可具体化
// 模式变量 s 类型为 List<?>(擦除后)
#
★★

23. Java 17+ 反射模块化(强封装)与 --add-opens

Java 17+ 反射模块化(强封装)与 --add-opens 是什么?

  • 强封装(JEP 403)
  • --add-opens
  • 反射框架影响

JDK 17 起强封装 Java 内部 API(JEP 403):默认隐藏 JDK 内部 API(如 sun.misc.Unsafe 的部分、内部类),反射访问受限。--add-opens 是 JVM 参数,用于开放指定模块的包给反射访问:--add-opens java.base/java.lang=ALL-UNNAMED。它允许反射框架(Spring、Hibernate、Jackson)访问被强封装的 JDK 内部/私有成员。用途:迁移遗留应用时,用 --add-opens 开放必要的包;反射框架依赖它访问 JDK 内部。安全风险:过度开放削弱强封装保护。应对:尽可能用公开 API,需要时最小化 --add-opens。

JDK 17 强封装内部 API,反射框架需 --add-opens 开放特定包才能访问,最小化开放以控制风险。

// JVM 参数:开放 java.base 的 java.lang 包给反射
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar
// 或开放其他模块
java --add-opens com.example.model=ALL-UNNAMED -jar app.jar
#
★★

24. Java 25 ScopedValue(JEP 506)与反射访问的关系

Java 25 ScopedValue(JEP 506)与反射访问的关系是什么?

  • ScopedValue
  • 反射访问
  • 线程本地数据

ScopedValue(JEP 506,JDK 25 终版)是线程局部数据的替代/改进机制,用 try-with-resources 绑定值到作用域,比 ThreadLocal 更安全(不可变、作用域明确、可继承给虚拟线程)。与反射访问的关系:ScopedValue 本身是普通 API,可通过反射访问(设置、读取),但反射访问 ScopedValue 破坏其作用域语义(应通过 try-with-resources 绑定)。反射访问 ScopedValue 内部字段受强封装限制(若需访问 JDK 实现需 opens)。关系上,ScopedValue 是数据传递机制,反射访问其字段/方法需遵循模块封装规则,但常规用法不依赖反射。它主要用于替代 ThreadLocal 传递上下文(traceId 等),与反射无直接绑定。

ScopedValue 是线程本地数据传递机制,与反射无直接关系;反射访问需遵循模块封装,常规用法用 try-with-resources 绑定。

static final ScopedValue<String> USER = ScopedValue.newInstance();
try (var scope = ScopedValue.where(USER, "alice")) {
    String u = USER.get();   // 作用域绑定
}
// 反射访问 ScopedValue 字段受强封装限制,常规不依赖反射
#
★★

25. Java 8 Lambda 与目标类型推断

Java 8 Lambda 与目标类型推断是什么?

  • Lambda 目标类型
  • 目标类型推断
  • 多态

Lambda 表达式的类型是"目标类型"(target type)——由上下文(赋值、参数、返回)推断的函数式接口类型。Lambda 本身无类型,需在函数式接口上下文中使用,编译器根据目标类型推断其参数与返回类型。目标类型推断支持:赋值(Runnable r = () -> ...)、方法参数(list.forEach(x -> ...))、返回(return () -> ...)、cast 等。Lambda 与函数式接口的抽象方法签名匹配即有效。目标类型推断让 Lambda 简洁(无需显式写接口类型),但也要求上下文明确(单一目标类型,否则报错)。Lambda 是"语法糖",实际生成函数式接口实例。

Lambda 类型由目标函数式接口上下文推断,无自含类型。理解目标类型推断才能理解 Lambda 的适用上下文。

// Lambda 类型由上下文推断
Runnable r = () -> System.out.println("hi");   // 目标类型 Runnable
list.forEach(x -> System.out.println(x));      // 目标类型 Consumer
Function<String, Integer> f = s -> s.length(); // 目标类型 Function
#
★★

26. Java 泛型类型擦除的实现机制与运行时影响

Java 泛型类型擦除的实现机制与运行时影响是什么?

  • 类型擦除机制
  • 运行时影响
  • 桥接方法

泛型类型擦除(type erasure):Java 编译器在编译时把泛型类型信息擦除,替换为边界类型(如 擦除为 Object 或上界),运行时 JVM 不知道泛型参数。实现:编译时把泛型类/方法中的类型参数替换为上界,插入强转,生成桥接方法(bridge method)保持多态。运行时影响:1)运行时无法获取泛型参数(List 与 List 运行时相同);2)instanceof 无法检查泛型参数;3)无法创建泛型数组(new T[]);4)可能产生强转的 ClassCastException;5)反射可通过 signature 属性获取泛型信息(编译期保留在字节码)。擦除是"编译期语法 + 运行时擦除"的兼容性设计。

擦除把泛型参数替换为上界并插入强转,运行时无泛型信息,影响 instanceof/数组/反射,桥接方法保多态。

// 擦除后:List<String> 与 List<Integer> 都是 List
List<String> ls = new ArrayList<>();
List<Integer> li = new ArrayList<>();
ls.getClass() == li.getClass();   // true(运行时相同)
// if (obj instanceof List<String>)  // 编译错误
// T[] arr = new T[10];  // 编译错误(无法创建泛型数组)
#
★★

27. List 与 List 在运行时的相等性

List 与 List 在运行时的相等性是什么?

  • 类型擦除
  • 运行时 Class
  • 泛型参数不可见

由于类型擦除,List 与 List 在运行时是同一个 Class(java.util.List/LinkedList 等),它们的 getClass() 相等(如都是 ArrayList 的 Class),因为泛型参数在运行时被擦除。因此运行时"相等性"(Class 层面)相同,无法区分 List 与 List 。但对象内容的相等性(equals)取决于元素:List 与 List 若元素不同则 equals 不等。运行时类型检查(instanceof)也无法区分泛型参数。结论:运行时 Class 层面相等(擦除),业务内容由元素决定,泛型参数仅编译期可见。

擦除使 List 与 List 运行时 Class 相同,无法区分泛型参数;内容相等性仍由元素决定。

List<String> ls = new ArrayList<>();
List<Integer> li = new ArrayList<>();
ls.getClass() == li.getClass();   // true(运行时都是 ArrayList)
ls.equals(li);   // 若元素都空则 true,内容相等
// 无法用 instanceof 区分泛型参数
#
★★

28. Lookup 与 MethodHandle 的查找语义

Lookup 与 MethodHandle 的查找语义是什么?

  • MethodHandles.Lookup
  • MethodHandle 查找
  • 访问控制

MethodHandles.Lookup 用于查找 MethodHandle(方法句柄),查找语义:lookup 通过 MethodHandles.lookup() 获取,代表"调用者的访问上下文"(访问权限继承自调用类)。用 lookup.findVirtual/findStatic/findConstructor/findSpecial 查找方法/构造器/字段,返回 MethodHandle。访问控制:Lookup 的查找受"调用者模块与类"的访问权限约束——只能查找调用者有权限访问的方法(强封装下跨模块受限)。findVirtual 按虚方法(动态分派)、findStatic 按静态方法、findConstructor 按构造器。Unreflect 方式可把 Method/Field 转成 MethodHandle。查找语义核心是"访问权限 + 方法类型"。

Lookup 封装调用者的访问上下文,用 findVirtual/findStatic 等查找方法生成 MethodHandle,访问权限受模块/类约束。

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(String.class, "length", MethodType.methodType(int.class));
int len = (int) mh.invoke("hello");   // 5
#
★★

29. MethodHandle 与 VarHandle 的能力边界

MethodHandle 与 VarHandle 的能力边界是什么?

  • MethodHandle 方法调用
  • VarHandle 字段访问
  • 能力边界

MethodHandle 与 VarHandle 都是 Variable Handles API 的一部分,但能力边界不同:MethodHandle 用于"方法调用"(invoke/invokeExact),封装方法、构造器、字段访问器,可做动态方法分派、参数绑定(bindTo)、组合(asType、combine);VarHandle 用于"字段/数组元素的原子访问"(get/set/compareAndSet/atomic 操作),关注内存访问与原子性。边界:MethodHandle 侧重"方法调用能力"(JIT 可内联优化,比反射快),VarHandle 侧重"字段原子操作"(替代 AtomicReferenceFieldUpdater/Unsafe)。MethodHandle 也能访问字段(通过 findGetter/findSetter),但 VarHandle 专用于字段的原子/内存访问。选择:调用方法用 MethodHandle,字段原子操作用 VarHandle。

MethodHandle 面向方法调用(可内联、参数绑定),VarHandle 面向字段/数组原子访问。二者互补,MethodHandle 偏调用、VarHandle 偏内存原子。

// MethodHandle:方法调用
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "length", MethodType.methodType(int.class));
// VarHandle:字段原子访问
VarHandle vh = MethodHandles.lookup().findVarHandle(C.class, "x", int.class);
vh.compareAndSet(obj, 1, 2);
#
★★

30. MethodHandle(JDK 7+)与 LambdaMetafactory 在高性能反射调用中的取舍

MethodHandle(JDK 7+)与 LambdaMetafactory 在高性能反射调用中的取舍是什么?

  • MethodHandle 调用
  • LambdaMetafactory
  • 高性能反射取舍

MethodHandle 与 LambdaMetafactory 都用于高性能调用,优劣:MethodHandle:直接调用方法句柄,JIT 可内联优化(invoke/invokeExact),比反射(Method.invoke)快,但需一次查找(findVirtual)开销;LambdaMetafactory:把 Lambda 表达式/方法引用编译为 invokedynamic 调用点,运行期生成函数式接口实例,JIT 内联,性能极高,适合"同一方法多次调用"。取舍:MethodHandle 适合"动态查找并调用方法"(调用点固定、可内联),LambdaMetafactory 适合"把方法绑定为函数式接口"(如高性能回调、避免反射开销)。两者都替代反射提升性能,选择依据:需要动态方法句柄用 MethodHandle,需要函数式接口实例用 LambdaMetafactory。

MethodHandle 直接调用方法句柄可内联,LambdaMetafactory 生成函数式接口实例并内联,都比反射快。按"动态调用 vs 函数式绑定"选择。

// MethodHandle:动态调用
MethodHandle mh = lookup.findVirtual(S.class, "m", MethodType.methodType(void.class));
mh.invoke(s);   // 可内联
// LambdaMetafactory:生成函数式接口
CallSite cs = LambdaMetafactory.metafactory(lookup, "apply", ...);
#
★★

31. ParameterizedType、WildcardType、TypeVariable 与 GenericArrayType 分别描述哪些反射类型结构

ParameterizedType、WildcardType、TypeVariable 与 GenericArrayType 分别描述哪些反射类型结构?

  • ParameterizedType 参数化类型
  • WildcardType 通配符
  • TypeVariable 类型变量

四个 Type 子接口描述不同泛型结构:1)ParameterizedType:参数化类型(如 List 、Map<K,V>),有 getRawType()(原始类型)、getActualTypeArguments()(实参);2)WildcardType:通配符(如 ? extends Number、? super T),有 getUpperBounds()/getLowerBounds();3)TypeVariable:类型变量(如 T、E),有 getBounds()(上界)、getName();4)GenericArrayType:泛型数组(如 T[]、List[]),有 getGenericComponentType()。反射 getGenericType 返回这些 Type,用于解析泛型结构。

四个 Type 子接口分别描述参数化类型、通配符、类型变量、泛型数组,是反射解析泛型的四个结构。

Type t = field.getGenericType();   // 例如 List<String>
if (t instanceof ParameterizedType pt) {
    Type raw = pt.getRawType();            // List
    Type[] args = pt.getActualTypeArguments();  // String
}
// WildcardType: ? extends Number
// TypeVariable: T
// GenericArrayType: T[]
#
★★

32. Recursive Type Bound(<T extends Comparable>)的应用

Recursive Type Bound(<T extends Comparable>)的应用是什么?

  • 递归类型上界
  • Comparable 自引用
  • 泛型约束

递归类型上界(Recursive Type Bound)是类型变量受"自身参与"的泛型约束,典型如 <T extends Comparable<T>>:T 必须实现 Comparable(能与自身比较)。应用:约束泛型类型必须可比较自身,用于排序/比较的泛型方法(如 Collections.sort 之前、泛型 max/min)。类似增强:<T extends Comparable<? super T>> 允许 T 的父类实现 Comparable,更灵活。递归上界保证类型参数自身可比较,是泛型约束"自引用"的典型用法,用于"必须支持与自身比较/订阅"的泛型算法。

递归类型上界 <T extends Comparable> 约束 T 必须可比较自身,用于泛型排序/比较算法,保证类型安全。

static <T extends Comparable<T>> T max(List<T> list) {
    T best = list.get(0);
    for (T t : list) if (t.compareTo(best) > 0) best = t;
    return best;
}
// 更灵活:<T extends Comparable<? super T>>
static <T extends Comparable<? super T>> T min(List<T> list) { ... }
#
★★

33. ServiceLoader 的懒加载(lazy iteration)与 @AutoService 注解自动生成 META-INF/services 配置

ServiceLoader 的懒加载(lazy iteration)与 @AutoService 注解自动生成 META-INF/services 配置是什么?

  • ServiceLoader 懒加载
  • META-INF/services
  • @AutoService 自动生成

ServiceLoader(Java SPI)加载服务提供者:通过 META-INF/services/<接口全名> 文件列出实现类,运行时用 ServiceLoader.load(接口) 加载。懒加载:ServiceLoader 迭代(iterator)是惰性的——只有在迭代时按需实例化服务实现类,不立即加载全部(懒加载,避免启动时加载所有实现)。@AutoService(Google Auto 注解)是编译期注解处理器,自动生成 META-INF/services 配置文件,避免手动维护服务注册文件。应用:ServiceLoader.load 返回迭代器,for 循环逐个实例化(懒加载);@AutoService 注解在实现类上自动生成注册。这使 SPI 扩展点自动注册、懒加载。

ServiceLoader 懒加载(迭代时按需实例化),@AutoService 编译期自动生成 META-INF/services,避免手动维护注册。

// 实现类标注 @AutoService(自动生成 META-INF/services)
@AutoService(MyService.class)
public class MyServiceImpl implements MyService {}
// 客户端:懒加载
ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService s : loader) { ... }   // 迭代时按需实例化
#
★★

34. StackWalker 替代 getStackTrace 的取舍

StackWalker 替代 getStackTrace 的取舍是什么?

  • StackWalker(JDK 9+)
  • getStackTrace 的缺点
  • 取舍

StackWalker(JDK 9+)提供更高效的栈遍历:用 walk 函数式遍历栈帧,可选择只取需要的信息(类名、方法名、行号),不创建完整的 StackTraceElement 数组。getStackTrace/Thread.getStackTrace 会创建完整栈数组(分配所有 StackTraceElement),成本高、且无法只取部分。取舍:StackWalker 更高效(按需遍历、可过滤、可只取所需的帧)、支持 SHOW_HIDDEN_FRAMES 隐藏帧、延迟求值;getStackTrace 简单但开销大(分配完整数组)。需要栈信息时推荐 StackWalker(尤其高频、只取帧名/行号),getStackTrace 用于简单场景。StackWalker 也支持反射(getDeclaringClass)。

StackWalker 按需、高效遍历栈帧,避免 getStackTrace 创建完整数组的开销,适合只取部分栈信息的场景。

// 高效:只取需要的帧
String caller = StackWalker.getInstance()
    .walk(s -> s.limit(2).skip(1).findFirst())
    .map(StackWalker.StackFrame::getClassName).orElse("unknown");
// 传统:创建完整数组(开销大)
StackTraceElement[] st = Thread.currentThread().getStackTrace();
#
★★

35. TypeToken(Gson)解决泛型擦除的工程方案

TypeToken(Gson)解决泛型擦除的工程方案是什么?

  • Gson TypeToken
  • 泛型擦除
  • 反序列化泛型

Gson 的 TypeToken 解决泛型擦除:new TypeToken<List<Map<String, Integer>>>() {}.getType() 创建匿名子类,通过 getGenericSuperclass() 捕获泛型实参 Type(List<Map<String,Integer>>),Gson 反序列化时按完整泛型构造,避免擦除丢失 List 等。工程方案:TypeToken 匿名子类让编译器在 class 文件 signature 属性保留泛型信息,运行时反射 getGenericSuperclass 读取。Spring 的 ParameterizedTypeReference 与 Jackson 的 TypeReference 都是同类方案。TypeToken 用于"反序列化/序列化泛型集合或嵌套对象"时保留类型。

TypeToken 用匿名子类捕获泛型实参(getGenericSuperclass 读 signature),Gson 按完整泛型反序列化,解决擦除。

Type type = new TypeToken<List<Map<String, Integer>>>() {}.getType();
List<Map<String, Integer>> data = gson.fromJson(json, type);
#
★★

36. sun.misc.Unsafe 的禁用与替代

sun.misc.Unsafe 的禁用与替代是什么?

  • sun.misc.Unsafe 的风险
  • 禁用与强封装
  • 替代方案

sun.misc.Unsafe 提供底层内存操作(内存分配、CAS、数组元素访问、park 等),但它是 JDK 内部 API,不稳定且危险(可破坏内存安全)。JDK 9+ 强封装限制其访问(默认模块未导出),官方标记为受限/计划移除。替代方案:1)原子操作用 VarHandle(compareAndSet、getAndAdd);2)内存访问用 java.nio(ByteBuffer、MemorySegment);3)线程调度用 LockSupport;4)CAS 用 Atomic* 类;5)字段访问用 VarHandle/反射。JDK 18+ 的 MemorySegment(Foreign Function & Memory API)替代 Unsafe 内存操作。禁用 Unsafe 是为了安全与稳定性,应迁移到标准 API。

Unsafe 是危险内部 API,受强封装限制,替代方案:VarHandle(原子)、MemorySegment(内存)、LockSupport(park)、Atomic 类。

// 反模式:Unsafe 内存操作
// Unsafe U = Unsafe.getUnsafe();  // 强封装下受限
// 替代:
VarHandle vh = ...;  vh.compareAndSet(obj, a, b);   // 原子
MemorySegment seg = Arena.global().allocate(1024);   // 内存
LockSupport.park();   // 线程阻塞
#
★★

37. super 通配符的写入限制与读取灵活性

super 通配符的写入限制与读取灵活性是什么?

  • ? super T 下界
  • 写入限制/读取灵活性
  • PECS

? super T 表示"T 或其父类型的未知类型"(下界通配符)。写入限制:可以写入 T(或 T 的子类)的值,因为任何 T 的父类型(下界是 T)都能接受 T 实例;但字段类型是"某个未知的父类型",读取时只能当作 Object(无法确定具体类型),故读取灵活性低(只能读 Object)。即:List<? super Integer> 可以 add(Integer)(写),但 get 返回 Object(读受限)。写入灵活性:可写 T 及子类;读取限制:只能读 Object。这是 PECS 的"生产者 extends,消费者 super"——super 用于消费者(只写)。

? super T 可写 T 及子类(get 只能读 Object),符合"消费者 super"(Predicate 只消费)。写入灵活、读取受限。

List<? super Integer> list = new ArrayList<Number>();
list.add(1);         // 可写 Integer
list.add(2);
Object o = list.get(0);   // 读取只能 Object(不能确定具体类型)
// 不能 list.add("x")(String 不是 Integer 的父类)
#
★★

38. 两个泛型重载经类型擦除后签名相同为何无法共存,修改返回类型能否解决这种冲突

两个泛型重载经类型擦除后签名相同为何无法共存?修改返回类型能否解决这种冲突?

  • 泛型重载擦除
  • 方法签名
  • 返回类型不参与签名

两个泛型重载如 void m(List<String>)void m(List<Integer>),经类型擦除后都变成 m(List),方法签名相同,JVM 无法区分,编译报错("name clash")。方法签名包括方法名 + 参数类型,不含返回类型。修改返回类型不能解决:Java 不允许仅返回类型不同的重载(JVM 方法描述符含返回类型,但 Java 语言规则不允许仅返回类型不同的重载),且擦除后参数类型相同,返回类型不同也不够区分(Java 层不允许)。解决方案:用不同方法名、或用不同参数类型(非擦除冲突)。结论:擦除冲突无法通过改返回类型解决,需改方法名或参数。

泛型重载擦除后参数签名相同冲突,返回类型不参与签名且 Java 不允许仅返回类型不同的重载,需改方法名/参数。

// 冲突:擦除后都是 m(List)
// void m(List<String> l) {}
// void m(List<Integer> l) {}   // 编译错误:name clash
// 解决:改方法名
void mString(List<String> l) {}
void mInt(List<Integer> l) {}
#
★★

39. 交叉类型上界如何同时约束父类和多个接口,类型变量声明时为什么父类必须排在最前

交叉类型上界如何同时约束父类和多个接口?类型变量声明时为什么父类必须排在最前?

  • 交叉类型上界(&)
  • 父类必须最前
  • 多接口约束

交叉类型上界(intersection type)用 & 连接多个类型:<T extends Foo & Bar & Baz> 表示 T 必须同时是 Foo 的子类(或自身)且实现 Bar、Baz。用 & 可同时约束一个父类和多个接口。父类必须排在最前:类型变量声明中最多只有一个类(class),且必须放在第一位(<T extends Foo & Bar>),因为 Java 单继承,类只能有一个父类,接口可多个;若父类不排最前(如 <T extends Bar & Foo> 且 Foo 是类),编译器报错("class 必须在第一个")。原因:类型变量只有一个类上界,接口可有多个,语法强制类在前。交叉类型上界用于"同时要求继承关系 + 实现多个接口"。

交叉类型上界用 & 同时约束父类与多个接口,父类必须排最前(单继承只能一个类上界,接口可多个)。

// 交叉类型上界:T 实现 Comparable 且是 Serializable
static <T extends Comparable<T> & Serializable> void sort(T t) { ... }
// 父类必须最前(若有类)
// <T extends Base & Runnable & Serializable>  // 正确
// <T extends Runnable & Base>  // 若 Base 是类,编译错误
#
★★

40. 原始类型(Raw Type)与泛型类型的兼容性陷阱

原始类型(Raw Type)与泛型类型的兼容性陷阱是什么?

  • raw type 与泛型混用
  • unchecked 警告
  • 运行时 CCE

原始类型(如 List 而非 List )与泛型类型混用会引入兼容性陷阱:1)使用 raw type 会收到 unchecked 警告(编译器无法检查类型);2)raw type 与泛型互操作时,编译期可能通过但运行时抛 ClassCastException(如把 List 传给期望 List 的 raw 参数,内部强转失败);3)raw type 丢失类型安全,破坏泛型约束。兼容性陷阱:向 modern 泛型代码传入 raw type 数据,编译器无法验证,潜在 CCE。避免:始终使用参数化类型,迁移遗留 raw 代码,处理 unchecked 警告。raw type 是历史遗留,仅用于与旧代码兼容。

raw type 与泛型混用产生 unchecked 警告与潜在运行时 CCE(类型安全丢失),应始终参数化并迁移遗留代码。

List raw = new ArrayList();        // raw type,unchecked
raw.add("a");
List<Integer> list = raw;          // 编译警告(unchecked)
Integer x = list.get(0);           // 运行时 ClassCastException("a" 非 Integer)
#
★★

41. 反射获取 Class 对象的三种途径与性能影响

反射获取 Class 对象的三种途径是什么?性能影响如何?

  • Class.forName
  • 类名.class
  • obj.getClass()

获取 Class 对象三种途径:1)Class.forName("全限定名"):运行时按字符串加载类,可能触发类初始化(静态块),性能相对慢(字符串查找+类加载);2)类名.class(如 String.class):编译期常量,最轻量,不触发初始化,性能最好;3)obj.getClass():运行时获取对象实际运行类,性能好(直接读对象头)。性能影响:.class 是编译期常量无开销、getClass() 是运行时快速读取、forName 需字符串解析与类加载(可能初始化),最慢。选择:编译期已知类型用 .class,运行时用 getClass,需要按名称动态加载用 forName。

.class 编译期常量最轻、getClass() 运行时快速、forName 需字符串加载可能初始化最慢。按场景选途径。

Class<String> c1 = String.class;                          // 编译期常量,最快
Class<?> c2 = "abc".getClass();                          // 运行时类型
Class<?> c3 = Class.forName("java.lang.String");         // 字符串加载,可能初始化,最慢
#
★★

42. 反射调用方法与构造器的性能开销与 setAccessible

反射调用方法与构造器的性能开销与 setAccessible 是什么?

  • 反射性能开销
  • setAccessible 优化
  • 替代方案

反射调用方法/构造器比直接调用慢:需要方法查找、参数装箱/拆箱、访问检查、方法分发(Method.invoke 走 invoke 反射路径),且难以被 JIT 内联优化。setAccessible(true) 可跳过访问权限检查,减少部分开销(但仍比直接调用慢)。性能优化:1)缓存 Method/Constructor 对象(避免重复查找);2)setAccessible(true) 跳过访问检查;3)用 MethodHandle/LambdaMetafactory 替代反射(可内联、更快);4)避免在热路径用反射。构造器反射也需 newInstance(JDK 9+ 用 getDeclaredConstructor().newInstance())。setAccessible 在强封装下受限。

反射慢(查找、装箱、访问检查、难内联),setAccessible 跳过检查提速但仍有开销,热路径用 MethodHandle 或缓存。

// 缓存 Method + setAccessible 提速
Method m = cls.getDeclaredMethod("x");
m.setAccessible(true);       // 跳过访问检查
m.invoke(obj);               // 反射调用
// 更优:MethodHandle 可内联
#
★★

43. 哪些类型属于可具体化类型,为什么可以检查 instanceof List<?> 却不能检查 List

哪些类型属于可具体化类型?为什么可以检查 instanceof List<?> 却不能检查 List?

  • 可具体化类型(reifiable)
  • instanceof 限制
  • 泛型参数不可具体化

可具体化类型(reifiable)是运行时"完全确定"的类型,包括:基本类型、非泛型类/接口类型、原始类型、无界通配符泛型(List)。不可具体化类型:含类型参数的参数化类型(List 、List )、类型变量等。instanceof 只能检查可具体化类型:List 可检查(运行时 List 本身存在,? 表示任意),List 不可检查(擦除后无 String 信息,无法区分 List 与 List )。所以 instanceof List<?> 合法、instanceof List 编译错误。原因:擦除使泛型参数运行时不可见,只能检查原始类型名。

可具体化类型运行时确定(List<?>、原始类型),不可具体化类型含类型参数(List),instanceof 只能检查可具体化类型。

if (obj instanceof List<?>) { ... }   // 合法(可具体化)
// if (obj instanceof List<String>) {}  // 编译错误(不可具体化)
if (obj instanceof String) { ... }     // 合法(非泛型)
#
★★

44. 泛型 record 的规范构造器如何校验组件不变式,静态成员为何不能引用 record 的类型参数

泛型 record 的规范构造器如何校验组件不变式?静态成员为何不能引用 record 的类型参数?

  • 泛型 record 构造器
  • 组件不变式校验
  • 静态成员与类型参数

泛型 record(如 record Box(T value))的规范构造器(含紧凑构造器)用于校验组件不变式:在构造器体内检查组件约束(如 null 检查、范围),确保创建的 record 满足不变量。静态成员不能引用 record 的类型参数:静态成员(static 字段/方法)属于类本身,不绑定具体类型参数实例;泛型类型参数 T 只对实例(每个实例的具体类型)有意义,静态成员无法关联到具体 T,因此静态成员不能使用 T(编译错误)。原因:静态成员与实例分开,类型参数是实例级,静态上下文没有 T 的实例类型。应通过实例方法或具体类型处理。

泛型 record 构造器校验组件不变式;静态成员与类型参数无关(类级 vs 实例级),不能引用 T。

record Box<T>(T value) {
    Box { if (value == null) throw new IllegalArgumentException(); }  // 校验不变式
    // static T get() { return value; }  // 编译错误:静态成员不能引用 T
    T get() { return value; }   // 实例方法可用 T
}
#
★★

45. 泛型与数组协变的兼容性问题

泛型与数组协变的兼容性问题是什么?

  • 数组协变
  • 泛型不变
  • 兼容性

数组是协变的(covariant):String[] 是 Object[] 的子类型,因为数组类型是运行时可具体化的(reified)。泛型是不变的(invariant):List 不是 List 的子类型。兼容性问题:数组协变 + 泛型不变在混用时产生类型安全问题。例如 Object[] arr = new String[10] 合法(协变),但 arr[0] = 1 运行时抛 ArrayStoreException;而 List 不能接收 List (泛型不变,编译错误)。泛型与数组不可混用:不能创建泛型数组(new T[]、new List []),因为泛型擦除后无法保证数组的运行时类型安全(数组协变 + 擦除会允许写入错误类型)。因此泛型数组被禁止,用集合替代。

数组协变 + 泛型不变:数组运行时检查(ArrayStoreException),泛型编译期检查。泛型数组因擦除+协变不安全被禁止。

Object[] arr = new String[10];   // 协变合法
arr[0] = 1;                       // 运行时 ArrayStoreException
// List<Object> lo = new ArrayList<String>();  // 编译错误(泛型不变)
// List<String>[] arr = new List<String>[10];  // 编译错误(泛型数组)
#
★★

46. 泛型接口实现时的桥接方法(Bridge Method)

泛型接口实现时的桥接方法(Bridge Method)是什么?

  • 桥接方法
  • 泛型擦除
  • 多态保持

桥接方法(bridge method)是编译器在泛型擦除后生成的"合成方法",用于保持虚方法分派的正确性。示例:接口 interface I<T> { T get(); },实现 class C implements I<String> { public String get() { ... } }。擦除后接口方法变 Object get(),但 C 提供的是 String get()(签名不同,不覆盖)。编译器为 C 生成桥接方法 Object get()(合成),它调用 String get(),使接口的 Object get() 正确分派到 C 的 String get()。桥接方法返回值是 Object(或调整参数),实现泛型擦除后的多态。它保证"擦除前后"方法签名一致(JVM 方法分发正确)。

桥接方法在泛型擦除后生成,桥接签名使接口/父类的 Object 方法正确分派到具体类型方法,保持多态。

interface I<T> { T get(); }
class C implements I<String> {
    public String get() { return "x"; }
    // 编译器生成桥接方法:public Object get() { return this.get(); }(合成)
}
#
★★

47. 泛型数组为何不能直接创建(new T[])

泛型数组为何不能直接创建(new T[])?

  • 泛型数组创建
  • 类型擦除
  • 数组协变安全

new T[] 被禁止,因为泛型类型参数 T 在运行时被擦除,无法确定数组元素的具体类型,而数组是运行时协变且会检查元素类型(ArrayStoreException)。若允许 new T[],运行时无法知道 T 是 String 还是 Integer,无法保证数组类型安全(数组协变 + 擦除会允许写入错误类型)。因此泛型数组直接创建被禁止(编译错误)。解决:用 (T[]) new Object[n](强转,但产生 unchecked 警告,运行时可能 CCE),或使用集合(List)替代。工程上优先用集合,避免泛型数组。

泛型数组创建被禁止因擦除后无法确定元素类型且数组协变不安全,用 List 或强转 Object[] 替代。

// T[] arr = new T[10];  // 编译错误
@SuppressWarnings("unchecked")
T[] arr = (T[]) new Object[10];   // 强转,unchecked 警告
List<T> list = new ArrayList<>();  // 推荐:集合替代
#
★★

48. 泛型方法、泛型类与类型推断的边界

泛型方法、泛型类与类型推断的边界是什么?

  • 泛型方法
  • 泛型类
  • 类型推断

泛型方法:方法级声明类型参数(<T> T method(T t)),类型在调用时推断,如 Collections.emptyList() 通过目标类型推断。泛型类:类级声明类型参数(class Box<T>),实例化时指定类型(new Box<String>())。类型推断边界:1)泛型方法参数与返回值可推断,但有时需显式类型参数(Collections.<String>emptyList());2)泛型类实例化需指定类型(或 diamond 推断 new Box<>());3)类型推断受赋值上下文、参数上下文、返回类型约束;4)泛型方法不能用于静态上下文引用类型参数(重复)。类型推断让泛型更简洁,但复杂场景需显式指定。

泛型方法在调用时推断、泛型类在实例化时指定,类型推断受上下文约束,复杂场景需显式类型参数。

// 泛型方法:调用时推断
static <T> T identity(T t) { return t; }
String s = identity("x");        // 推断 T=String
// 泛型类:实例化指定
Box<String> b = new Box<>();     // diamond 推断
// 显式类型参数
Collections.<String>emptyList();
#
★★

49. 泛型类为什么不能直接或间接继承 Throwable,catch 子句又为何不能捕获类型变量

泛型类为什么不能直接或间接继承 Throwable?catch 子句为何不能捕获类型变量?

  • 泛型类不能继承 Throwable
  • catch 不能捕获类型变量
  • 擦除与异常

泛型类不能继承 Throwable(直接或间接):因为泛型擦除后,class MyException<T> extends Exception 会擦除为对象,但 JVM 的异常处理需要精确的异常类型(catch 匹配具体类型),擦除后的泛型异常无法确定具体类型,破坏异常匹配的精确性。且 JVM 规范禁止泛型类继承 Throwable。catch 子句不能捕获类型变量:catch (T e) 非法,因为异常类型必须在运行时具体化(reifiable),类型变量 T 被擦除,无法确定捕获的具体异常类型,JVM 无法精确匹配。异常处理需要具体化类型,而泛型类型变量/泛型类不可具体化,故不能用于异常继承与 catch。

泛型异常擦除后无法精确匹配/具体化,JVM 禁止泛型类继承 Throwable,catch 需具体化类型故不能捕获类型变量。

// class MyException<T> extends Exception {}  // 编译错误
// catch (T e) {}  // 编译错误:catch 不能是类型变量
// 异常类必须非泛型、具体化
class MyException extends Exception { ... }
#
★★

50. 泛型类的静态成员为何不能使用类类型参数,其限制的根因与规避方式是什么?

泛型类的静态成员为何不能使用类类型参数?其限制的根因与规避方式是什么?

  • 静态成员与类型参数
  • 根因
  • 规避方式

泛型类的静态成员(static 字段/方法)不能使用类类型参数 T:因为静态成员属于类本身(类级),不绑定具体类型参数实例;类型参数 T 是实例级(每个实例的类型不同),静态上下文没有 T 的具体类型。根因:类型参数是"实例级"的,静态成员与实例无关,无法确定 T。规避方式:1)静态方法用"方法级类型参数"(static <T> T method(T t)),类似泛型方法;2)静态字段用具体类型/Object 或泛型静态方法返回;3)避免静态成员依赖类型参数,用实例方法。泛型方法级类型参数是静态成员规避的常用方式。

类型参数是实例级,静态成员类级无 T,故不能用。规避:静态方法改用方法级类型参数,静态字段用具体类型。

class Box<T> {
    // static T s;  // 编译错误:静态成员不能使用类型参数
    static <U> U helper(U u) { return u; }   // 方法级类型参数,规避
    T get() { return value; }   // 实例方法可用 T
}
#
★★

51. 注解处理器(APT/KAPT/KSP)的发展

注解处理器(APT/KAPT/KSP)的发展是什么?

  • APT(注解处理工具)
  • KAPT/KSP
  • 编译期注解处理

注解处理器(Annotation Processor)在编译期读取注解并生成代码/文件,实现元编程。发展:APT(Annotation Processing Tool)最初是独立工具,Java 6 起并入 javac 成为标准(javax.annotation.processing.Processor,通过 META-INF/services 或 Processor SPI 注册)。KAPT(Kotlin Annotation Processing Tool)是 Kotlin 的注解处理器,桥接 Java 注解处理器到 Kotlin(生成 Java stub 供处理器处理)。KSP(Kotlin Symbol Processing)是 Kotlin 的符号处理 API,直接处理 Kotlin 符号,比 KAPT 更快(无需生成 stub、直接分析符号)。Java 端则用 javac 的注解处理器(如 Lombok、AutoService、Dagger)。发展:从 Java 标准 APT 到 Kotlin 的 KAPT/KSP,后者更快、更直接。

注解处理器在编译期生成代码。Java 用标准 APT,Kotlin 用 KAPT(桥接)或 KSP(直接符号处理,更快)。

// Java 注解处理器:javax.annotation.processing
@SupportedAnnotationTypes("com.example.MyAnnotation")
public class MyProcessor extends AbstractProcessor {
    @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { ... }
}
// 注册:META-INF/services/javax.annotation.processing.Processor
#
★★

52. 注解的保留策略(SOURCE/CLASS/RUNTIME)与读取机制

注解的保留策略(SOURCE/CLASS/RUNTIME)与读取机制是什么?

  • @Retention 策略
  • SOURCE/CLASS/RUNTIME
  • 读取机制

注解保留策略(@Retention)决定注解的保留范围:1)SOURCE:只保留在源码,编译后丢弃(如 @Override、@SuppressWarnings),不进入 class 文件;2)CLASS:保留在 class 文件字节码,但运行时不可读(默认),编译期/工具读取;3)RUNTIME:保留到运行时,可通过反射读取(isAnnotationPresent、getAnnotation)。读取机制:RUNTIME 注解用反射读取(getAnnotation/getAnnotations),CLASS 注解用字节码工具(ASM)或注解处理器读取,SOURCE 注解仅源码/apt 处理。选择:需要运行时反射读取用 RUNTIME,仅编译期处理用 CLASS 或 SOURCE。

@Retention 决定注解保留范围,RUNTIME 可反射读取,CLASS 字节码/工具读取,SOURCE 仅源码,按读取需求选择。

@Retention(RetentionPolicy.RUNTIME) @interface MyAnn {}
// 运行时反射读取
if (cls.isAnnotationPresent(MyAnn.class)) {
    MyAnn a = cls.getAnnotation(MyAnn.class);
}
#
★★

53. 类型推断在链式调用与方法引用(Method Reference)

类型推断在链式调用与方法引用(Method Reference)中如何工作?

  • 链式调用类型推断
  • 方法引用的目标类型
  • 推断约束

类型推断在链式调用中:list.stream().map(x -> x.length()).collect(Collectors.toList()),每一步的类型由前一步推断并约束后续(参数、返回值、目标类型共同约束)。方法引用(Method Reference)的类型推断:方法引用通过"目标函数式接口签名"推断类型参数(如 String::valueOf 按目标类型选重载),编译器结合目标类型与参数类型推断。推断约束:链式调用中赋值上下文、参数上下文、返回类型共同约束类型;方法引用若目标类型不明确(多个候选)需显式类型。类型推断让链式调用与方法引用简洁,但复杂场景需显式指定。

链式调用与方法引用都靠目标类型推断,受赋值/参数/返回上下文约束,复杂场景需显式类型参数。

// 链式调用类型推断
List<Integer> lens = list.stream().map(x -> x.length()).collect(Collectors.toList());
// 方法引用按目标类型推断
Function<String, Integer> f = String::length;   // 目标类型 Function
#
★★

54. 类型擦除后如何通过反射获取泛型信息

类型擦除后如何通过反射获取泛型信息?

  • 反射获取泛型
  • getGenericType
  • signature 属性

类型擦除后,运行时 Class 无泛型参数,但编译器在 class 文件的 signature 属性中保留了泛型签名,反射可读取。获取方式:getGenericSuperclass()(父类泛型)、getGenericInterfaces()(接口泛型)、getGenericType()(字段泛型)、getGenericParameterTypes()/getGenericReturnType()(方法参数/返回泛型)。这些返回 Type(ParameterizedType 等),可解析实际类型参数。原理:编译器把泛型写入 signature 属性,反射 getGenericXxx 读取并解析为 Type。示例:字段 List<String> field 的 getGenericType() 返回 ParameterizedType,getActualTypeArguments() 得 String。

擦除运行时无泛型,但 class 文件 signature 属性保留泛型签名,反射 getGenericXxx 读取并解析为 Type。

Field f = User.class.getDeclaredField("tags");   // List<String> tags
Type t = f.getGenericType();                     // ParameterizedType
if (t instanceof ParameterizedType pt) {
    System.out.println(pt.getActualTypeArguments()[0]);  // String
}
#
★★

55. 类型擦除(Type Erasure)在桥接方法(Bridge Method)

类型擦除(Type Erasure)在桥接方法(Bridge Method)中的作用是什么?

  • 擦除与桥接方法
  • 桥接方法作用
  • 多态保持

类型擦除后,泛型类型参数被替换为上界,导致泛型方法/接口的签名变化(如 T 变 Object)。桥接方法(bridge method)是编译器生成的合成方法,用于在擦除后保持多态正确性:当子类实现泛型接口(如 implements I 提供 String get()),而接口擦除后方法签名是 Object get(),编译器为子类生成 Object get() 桥接方法,调用 String get(),使接口的 Object get() 正确分派到子类的具体方法。桥接方法把擦除后的 Object 签名"桥接"到具体类型方法,保证多态分派与泛型边界的正确性。它是擦除的配套机制。

擦除后签名变化,桥接方法桥接 Object 签名到具体类型方法,保持多态分派正确,是擦除的配套机制。

interface I<T> { T get(); }
class C implements I<String> {
    public String get() { return "x"; }
    // 擦除后生成桥接:public Object get() { return this.get(); }
}
#
★★

56. 设计只读生产者和可写消费者 API 时,如何应用 PECS 又避免返回类型被通配符污染

设计只读生产者和可写消费者 API 时,如何应用 PECS 又避免返回类型被通配符污染?

  • PECS 原则
  • 生产者/消费者
  • 返回类型通配符污染

PECS(Producer Extends, Consumer Super):只读生产(提供元素)用 ? extends T,只写消费(接收元素)用 ? super T。应用到 API 设计:1)方法参数读数据(生产者)用 ? extends(如 containsAll(Collection<? extends E>));2)方法参数写数据(消费者)用 ? super(如 addAll(Collection<? extends E>)——实际上 addAll 是消费源,但源是生产者用 extends;往里填用 super);3)避免返回类型被通配符污染:返回类型若用 ? extends/super 会强迫调用方处理通配符(如 List<? extends T> 返回,调用方无法安全 add),应返回具体类型(List)或用辅助方法(PECS 通配符捕获)在内部消除通配符,外部暴露干净类型。关键:通配符用在参数(输入),返回类型尽量具体化,避免污染。

PECS 用通配符修饰参数(生产 extends、消费 super),但返回类型避免通配符(用具体类型或辅助方法消除),保持 API 干净。

// 参数:生产者 extends
void addAll(Collection<? extends E> src) { ... }
// 返回类型避免通配符:返回具体类型
public List<E> getItems() { return new ArrayList<>(items); }   // 而非 List<? extends E>
// 内部用通配符捕获辅助方法处理
#
★★

57. 通配符 ?、? extends T、? super T 的 PECS 原则

通配符 ?、? extends T、? super T 的 PECS 原则是什么?

  • 无界通配符 ?
  • ? extends T(上界)
  • ? super T(下界)

通配符三种:?(无界,任意类型,只能读 Object 不能写);? extends T(上界,T 或子类,可读 T 及其父类型接受,不能写因为未知具体子类型);? super T(下界,T 或父类,可写 T 及子类,读只能 Object)。PECS 原则:Producer Extends, Consumer Super——生产者(提供元素,只读)用 ? extends T(可安全读取 T 或父类型);消费者(接收元素,只写)用 ? super T(可安全写入 T 及子类)。扩展名:List<? extends T> 可读 T,List<? super T> 可写 T。无界 ? 用于"只读不关心类型"或"只调用不传参写"。

PECS:生产者用 extends(可读)、消费者用 super(可写)。无界 ? 用于任意类型只读。通配符决定读写能力。

// List<? extends Number>:只读(可读 Number),不能 add
void read(List<? extends Number> l) { Number n = l.get(0); }
// List<? super Integer>:可写 Integer,读只能 Object
void write(List<? super Integer> l) { l.add(1); }
// List<?>:任意类型,只读 Object
#
★★

58. 链式泛型方法调用发生目标类型推断时,赋值上下文、参数上下文和返回值如何共同约束类型

链式泛型方法调用发生目标类型推断时,赋值上下文、参数上下文和返回值如何共同约束类型?

  • 目标类型推断
  • 赋值/参数/返回上下文
  • 约束

链式泛型方法调用中,类型推断受多种上下文共同约束:1)赋值上下文:如 List<String> r = collect(...) 的赋值目标类型约束推断;2)参数上下文:方法参数类型约束泛型参数(如 map(x -> x.a()) 的参数类型推断);3)返回上下文:方法返回值类型受目标类型约束(如 emptyList() 通过赋值目标推断 String)。JDK 8+ 目标类型推断(poly expressions)让这些上下文联合推断:Lambda 是 poly expression,其类型由目标类型决定;链式调用中每个方法的类型参数由相邻上下文约束。当上下文冲突或不足时,需显式指定。

链式调用的类型推断由赋值、参数、返回上下文联合约束,poly expression(Lambda)类型由目标类型决定,冲突时显式指定。

// 赋值上下文 + 参数上下文 + 返回上下文联合推断
List<String> names = users.stream()
    .map(u -> u.getName())            // 参数 u 推断,返回 String
    .collect(Collectors.toList());    // 赋值目标 List<String>
// 无上下文时显式指定
List<String> empty = Collections.<String>emptyList();
#

59. JDK 25 中 JSpecify(null-safety 注解)

JDK 25 中 JSpecify(null-safety 注解)是什么?

  • JSpecify 注解
  • null-safety
  • 静态分析

JSpecify 是 JSpecify 组织制定的标准 null-safety 注解规范(@Nullable、@NonNull、@NullMarked、@NullUnmarked 等),用于让 Java 具备可空性描述能力,供静态分析工具(检查器框架、IDEA、编译期检查)进行空值分析。JDK 25 中相关演进:Java 的 null 处理与 JSpecify 注解协作,JDK 内部部分 API 使用空值注解,支持工具链的空值安全分析。JSpecify 提供统一注解,让库作者标注可空性,调用方与工具能检测潜在 NPE。它不是 JDK 内建语法,而是注解规范被工具识别。JDK 25 关注其与 Language Model/API 的协作。

JSpecify 提供标准空值注解(@Nullable/@NonNull),供静态分析工具进行空值安全分析,是 JDK 生态的空值安全约定。

// JSpecify 注解
import org.jspecify.annotations.Nullable;
public @NullMarked class Service {
    public @Nullable String find(@NonNull String id) { ... }  // 可空返回
}
#

60. JDK 25 原始类型与未检查警告的演进

JDK 25 原始类型与未检查警告的演进是什么?

  • 原始类型(raw type)
  • unchecked 警告
  • 演进

原始类型(raw type)的使用会产生 unchecked 警告,JDK 各版本对该警告的处理与检查演进:JDK 25 中,JEP 488(primitive types in patterns,JDK 24 预览)与原始类型相关,但更核心的是编译器对 raw type 与 unchecked 警告的提示增强。演进方向:JDK 25 强化对 raw type 的警告提示(如更明确的 unchecked 警告、Lombok 类工具友好),并逐步推动消除 raw type 使用。原始类型仍用于与旧代码兼容(如泛型 API 的遗留),但编译器警告促其迁移。JDK 25 的原始类型处理:识别 raw type 使用场景、控制 unchecked 警告范围。整体趋势是减少 raw type、提升类型安全。

JDK 25 强化 raw type 的 unchecked 警告提示,推动迁移到参数化类型,提升类型安全,但保留旧代码兼容。

List raw = new ArrayList();   // raw type,unchecked 警告
// javac -Xlint:unchecked 显示警告
List<String> list = new ArrayList<>();   // 参数化,类型安全
#

61. JEP 483(AOT Class Loading & Linking)

JEP 483(AOT Class Loading & Linking)是什么?

  • JEP 483
  • AOT 类加载与链接
  • 启动优化

JEP 483(AOT Class Loading & Linking,JDK 24)允许在启动时把类加载与链接(Ahead-of-Time)提前,通过缓存/预加载类,减少启动时间与内存占用。它针对"启动时类加载与链接开销"进行优化:应用启动时,把频繁加载的类预先加载并链接(生成 AOT 缓存),避免运行时重复类加载与链接(常量池解析、方法解析)。适用于容器化、短生命周期任务(需要快速启动)的场景。JEP 483 是启动优化的一部分,减少 JVM 启动/类加载开销,配合 CDS(Class Data Sharing)等提升启动性能。

JEP 483 提前进行类加载与链接(AOT),减少启动开销,与 CDS 配合优化启动性能,适合快速启动场景。

// JEP 483 通过 AOT 类加载链接优化启动
// 生成 AOT 缓存,启动时预加载类,减少类加载/链接开销
// 配合 -XX:CDS 与 CDS 存档
#

62. JEP 484 与 ASM/Javassist 的关系(标准 vs 第三方)

JEP 484 与 ASM/Javassist 的关系(标准 vs 第三方)是什么?

  • JEP 484 Class-File API
  • ASM/Javassist 第三方
  • 标准 vs 第三方

JEP 484(Class-File API,JDK 24 预览)为 JDK 引入标准化、稳定的 Class 文件解析/生成 API,作为 JDK 内部的类文件 API(供编译器、运行时工具使用)。它与第三方库 ASM/Javassist 的关系:ASM 和 Javassist 是第三方字节码操作库,已被广泛使用(Spring、Hibernate 的字节码增强);JEP 484 提供 JDK 标准类文件 API,作为替代/补充。关系是"标准 vs 第三方":JDK 标准 API 更规范、稳定、随 JDK 演进,但第三方库(ASM)生态成熟、功能丰富;JEP 484 让 JDK 自身用标准 API 处理类文件,减少对 ASM 的依赖。它不是替代 ASM,而是提供标准选项。

JEP 484 是 JDK 标准类文件 API,ASM/Javassist 是第三方库;JEP 484 提供标准选项,减少 JDK 对第三方依赖,但 ASM 生态仍广泛。

// JEP 484 Class-File API(JDK 24 预览)
// 标准 API 解析/生成 class 文件
// 对比:ASM(第三方)、Javassist(第三方)
#

63. JEP 484 在框架(Spring/Hibernate)二次开发中的潜在应用

JEP 484 在框架(Spring/Hibernate)二次开发中的潜在应用是什么?

  • JEP 484 应用
  • 框架字节码
  • 二次开发

JEP 484(Class-File API)在框架二次开发中的潜在应用:框架(Spring、Hibernate)常需生成/修改字节码(代理类、实体增强、DTO 生成),传统用 ASM/Javassist。JEP 484 提供 JDK 标准类文件 API,框架可用它生成/解析 class 文件,摆脱对第三方字节码库的强依赖,减少版本兼容问题(ASM 需随 JDK 字节码版本更新)。潜在应用:1)生成代理/增强类(字节码生成);2)解析已编译类(分析注解、字节码);3)生成 DTO/映射类。二次开发者利用 JEP 484 实现字节码增强,获得标准、稳定、随 JDK 演进的 API。但 JEP 484 还是预览,生态迁移需时间。

框架可用 JEP 484 生成/解析字节码(代理、增强),减少对第三方 ASM 依赖,获得标准稳定 API,但需等生态迁移。

// 框架用 JEP 484 生成代理/增强类字节码
// 替代 ASM 的 ClassWriter/ClassReader
// 解析类结构、生成方法、字段
#

64. Java 24+ Class-File API(JEP 484)

Java 24+ 的 Class-File API(JEP 484)是什么?

  • Class-File API
  • 解析/生成 class 文件
  • 预览特性

JEP 484(Class-File API,Java 24 预览)为 JDK 引入标准、稳定的 Class 文件解析与生成 API。它提供对 class 文件格式的类型化访问:读取(解析类文件到结构化对象)、遍历(访问类结构、方法、字段、注解、字节码)、生成(构建类文件)。设计目标:提供稳定、演变友好、随 JDK 支持的类文件 API,替代 JDK 内部对外部(ASM)的依赖,供编译器、运行工具、框架使用。它是预览特性(Java 24),需 --enable-preview;后续版本演进。核心能力:解析/生成 .class 文件,类型化访问类结构。

JEP 484 是 JDK 标准 Class-File API,类型化解析/生成 class 文件,预览特性(Java 24),供 JDK 与框架使用。

// Class-File API(预览)示例
// 读取 class 文件
ClassFile cf = ClassFile.of();
ClassModel cm = cf.parse(bytes);   // 解析为结构化模型
// 生成 class 文件
byte[] bytes = cf.build(ClassDesc.of("Foo"), ...);
#

65. Java SPI(ServiceLoader)机制的工作原理及其与 Spring SPI(SpringFactoriesLoader)的差异

Java SPI(ServiceLoader)机制的工作原理及其与 Spring SPI(SpringFactoriesLoader)的差异是什么?

  • ServiceLoader SPI
  • SpringFactoriesLoader
  • 差异

Java SPI(ServiceLoader):通过 META-INF/services/<接口全名> 文件列出实现类,ServiceLoader.load(接口) 加载并懒加载实例化,实现服务的可插拔扩展。Spring SPI(SpringFactoriesLoader):通过 META-INF/spring.factories 文件配置(键值对,key 为接口/类型,value 为实现类列表),加载 Spring 框架的自动配置、启动器等扩展。差异:1)配置文件:ServiceLoader 用 META-INF/services/<接口>,SpringFactoriesLoader 用 META-INF/spring.factories(一个文件多条目);2)加载方式:ServiceLoader 按接口文件加载,SpringFactoriesLoader 按 spring.factories 批量加载;3)用途:ServiceLoader 是 JDK 标准 SPI,SpringFactoriesLoader 是 Spring 框架的扩展机制;4)实例化:都懒加载/按需实例化。Spring 的 SpringFactoriesLoader 更像"配置驱动"的 SPI。

ServiceLoader 用 META-INF/services/<接口> 文件,SpringFactoriesLoader 用 META-INF/spring.factories 多条目配置,都是 SPI 扩展机制但文件与加载方式不同。

// Java SPI:META-INF/services/com.example.MyService
// ServiceLoader.load(MyService.class)
// Spring SPI:META-INF/spring.factories
// com.example.AutoConfiguration=com.example.MyAutoConfiguration
#

66. MethodHandle.Lookup.defineClass 在 JDK 25 的可访问性

MethodHandle.Lookup.defineClass 在 JDK 25 的可访问性是什么?

  • Lookup.defineClass
  • 动态类定义
  • 可访问性

MethodHandle.Lookup.defineClass(byte[]) 允许在运行时动态定义类(从字节码),用于动态代码生成。可访问性:由 Lookup 的"访问上下文"决定——生成的类被视为 Lookup 所在类的成员/上下文,可访问 Lookup 类可访问的成员;受模块与包约束:类定义在 Lookup 所在包的模块上下文中,且不能定义到其他模块/包(跨模块受限)。JDK 25 中 defineClass 的可访问性:沿用强封装与 Lookup 语义,生成的类继承 Lookup 的访问权限(可访问 Lookup 类的私有成员),但受模块边界限制(只能在 Lookup 的模块/包内定义)。用于动态生成代理/增强类,需在 Lookup 的可访问范围内。

Lookup.defineClass 动态定义类,生成类继承 Lookup 的访问上下文(可访问 Lookup 类成员),受模块/包边界限制。

MethodHandles.Lookup lookup = MethodHandles.lookup();
Class<?> dyclass = lookup.defineClass(bytes);   // 动态定义类
// 生成类可访问 Lookup 类的成员,受模块/包约束