1. 谈谈Java中的泛型原理,什么是类型擦除?有什么限制?
考察点 :泛型实现机制与局限性。
Java泛型是编译期行为,运行时JVM会擦除泛型类型信息,替换为原始类型(如List<String>变为List),并插入强制类型转换。限制包括:无法用基本类型作为泛型参数、无法实例化泛型类型、无法使用instanceof判断泛型类型、无法创建泛型数组等。
2. Java异常体系结构是怎样的?运行时异常和受检异常有什么区别?
考察点 :异常分类与处理规范。
顶层Throwable分Error(不可恢复)和Exception。Exception下分受检异常(Checked,如IOException,必须捕获或声明)和运行时异常(Unchecked,如NullPointerException,无需强制处理)。受检异常强调编译时校验,运行时异常代表编程错误。
3. 什么是反射?有哪些应用场景?反射的性能问题如何解决?
考察点 :动态性与性能权衡。
反射允许运行时获取类信息并操作对象/方法。应用场景:框架(Spring)、动态代理、注解处理、IDE智能提示。性能问题可通过缓存Method/Field对象、关闭访问检查(setAccessible(true))、使用MethodHandle(invokedynamic)或避免热点反射调用优化。
4. 动态代理的两种实现方式(JDK Proxy和CGLIB)有什么区别?各自适用场景?
考察点 :代理模式底层实现。
JDK Proxy基于接口,使用InvocationHandler+Proxy生成代理类,必须存在接口。CGLIB基于字节码生成子类,通过MethodInterceptor实现,无需接口但目标类不能final。Spring AOP默认若目标有接口则用JDK,否则CGLIB;CGLIB性能稍高但代理类创建更慢。
5. 内部类有哪几种?静态内部类和非静态内部类在加载和实例化上有什么区别?
考察点 :内部类生命周期与持有关系。
内部类分为静态内部类、成员内部类、局部内部类、匿名内部类。静态内部类不持有外部类引用,可独立存在,加载时无需外部类实例;非静态成员内部类隐式持有外部类this,必须先有外部类对象才能实例化,且不能包含静态成员。
6. try-with-resources语句的原理是什么?它是如何自动关闭资源的?
考察点 :资源管理优化。
try-with-resources要求资源类实现AutoCloseable接口(或Closeable)。编译后,资源在try块结束后自动调用close(),并处理异常抑制(addSuppressed)。原理是语法糖,生成字节码包含finally块和异常处理逻辑,简化了手动关闭代码。
7. Java中BIO、NIO、AIO的区别是什么?各自适用什么场景?
考察点 :IO模型演进与选型。
BIO(同步阻塞)每连接一线程,适合连接数少且稳定的场景;NIO(同步非阻塞)基于Selector多路复用,适合高并发连接但数据量小的场景(如聊天、网关);AIO(异步非阻塞)基于事件回调,适合连接数多且数据量大的场景(如文件IO),但实现复杂,Java中实际使用较少。
8. 类加载器的双亲委派模型是什么?为什么要这样设计?如何打破双亲委派?
考察点 :类加载机制与安全性。
双亲委派即类加载器收到加载请求后,先委派给父加载器,父加载器无法加载才自行加载。设计目的:避免核心类库被随意替换,保证类加载的唯一性和安全性。打破方式:自定义类加载器重写loadClass()(而非findClass()),或利用线程上下文类加载器(如SPI机制)。
9. 什么是Java内存模型(JMM)?谈谈你对happens-before规则的理解。
考察点 :并发可见性与有序性。
JMM定义了线程间内存操作的规范,保证多线程环境下正确性。happens-before规则是JMM的核心,它确保前一个操作的结果对后一个操作可见。常见规则:程序次序规则、volatile变量规则、锁规则、传递性等。遵守这些规则,程序员无需关心底层缓存一致性。
10. 请简述synchronized锁升级过程(偏向锁→轻量级锁→重量级锁)。
考察点 :JVM锁优化策略。
JDK 1.6后引入锁升级。无锁状态 → 偏向锁(只有单线程竞争,CAS替换线程ID)→ 轻量级锁(多线程交替竞争,CAS自旋)→ 重量级锁(自旋超过阈值或阻塞,使用操作系统互斥量)。升级不可逆,目的是减少重量级锁带来的上下文切换开销。
11. ThreadLocal的原理是什么?使用ThreadLocal时如何避免内存泄漏?
考察点 :线程本地变量与内存管理。
ThreadLocal内部维护ThreadLocalMap,键为弱引用ThreadLocal,值为强引用。内存泄漏根源:键被回收后,值依然存在且无法被访问。避免方法:使用后主动调用remove();将ThreadLocal声明为static(使其生命周期与类一致,弱引用回收时机明确);使用InheritableThreadLocal注意父子线程传递。
12. CAS(Compare And Swap)原理是什么?Java中哪些原子类基于CAS实现?ABA问题如何解决?
考察点 :非阻塞同步机制。
CAS是CPU原子指令,比较内存值与期望值,若相等则交换新值,否则失败重试。Java中AtomicInteger、AtomicLong、AtomicReference等基于Unsafe的CAS实现。ABA问题可通过版本号(AtomicStampedReference)或时间戳解决,也可用AtomicMarkableReference。
13. CompletableFuture相比Future有哪些改进?如何实现异步任务编排?
考察点 :异步编程增强。
Future只能阻塞获取结果或轮询,且无法链式回调。CompletableFuture实现CompletionStage,支持回调链(thenApply、thenCompose)、组合(thenCombine)、异常处理(exceptionally)、多任务聚合(allOf/anyOf)。其内部基于ForkJoinPool,通过完成状态驱动执行。
14. Stream API的中间操作和终端操作的区别?延迟执行是如何实现的?
考察点 :函数式编程与惰性求值。
中间操作(如filter、map)返回新Stream,惰性执行,仅记录操作;终端操作(如collect、forEach)触发实际遍历和计算。延迟执行通过构建操作链表,终端操作时触发"流水线"执行,避免不必要的计算,提升性能。
15. 新的日期时间API(LocalDate、LocalTime等)相比Date/Calendar有哪些优势?
考察点 :日期API设计缺陷与改进。
Date/Calendar可变、线程不安全、月份从0开始、时区处理混乱。新API不可变、线程安全、清晰分离日期/时间/时间戳(Instant)、支持时区(ZonedDateTime)、提供方便的计算方法(plus、until),且与java.time包统一。
16. Java中的注解有哪些元注解?@Retention和@Target分别的作用是什么?
考察点 :注解元数据。
元注解包括@Retention(保留策略:SOURCE/CLASS/RUNTIME)、@Target(作用目标:TYPE/METHOD/FIELD等)、@Documented、@Inherited、@Repeatable。@Retention控制注解是否在运行时保留(反射可见),@Target限定注解可修饰的程序元素。
17. 枚举(enum)为什么是线程安全的?枚举如何实现单例模式?
考察点 :枚举底层实现与单例最佳实践。
枚举由JVM保证每个常量在类加载时只实例化一次,且valueOf方法内部使用Class对象同步,因此线程安全且防止反射/序列化攻击。枚举单例写法简单,无需volatile或双重检查,是《Effective Java》推荐的实现方式。
18. Java序列化中serialVersionUID有什么作用?如果不显式声明会有什么问题?
考察点 :序列化版本一致性。
serialVersionUID用于验证反序列化时类版本是否兼容。若未显式声明,JVM会根据类信息动态计算,一旦类有改动(如字段增减),计算值变化,导致InvalidClassException。显式声明可控制兼容性,减少版本升级风险。
19. Java中强引用、软引用、弱引用、虚引用的区别是什么?各自的使用场景?
考察点 :引用类型与垃圾回收交互。
强引用(new)永不回收,OOM风险;软引用(SoftReference)内存不足时回收,适合缓存;弱引用(WeakReference)下次GC必回收,适合ThreadLocal键;虚引用(PhantomReference)无法获取对象,只用于跟踪对象回收(如NIO的直接内存清理)。
20. 自动装箱和拆箱的原理是什么?哪些场景下可能引发性能问题或空指针异常?
考察点 :基础类型与包装类型转换。
自动装箱调用valueOf()(有缓存优化,如Integer -128~127),拆箱调用xxxValue()。性能问题:循环内大量装箱创建对象;空指针异常:包装类型为null时拆箱(如Integer赋给int)。此外,包装类比较应使用equals而非==,避免缓存范围误解。