Java基础50道经典面试题(四)
-
-
- [💡 Java新特性与现代语法(11题)](#💡 Java新特性与现代语法(11题))
- [💡 面向对象与设计进阶(9题)](#💡 面向对象与设计进阶(9题))
- [💡 核心API与高级特性(9题)](#💡 核心API与高级特性(9题))
- [💡 JVM与性能基础(10题)](#💡 JVM与性能基础(10题))
- [💡 并发编程进阶(11题)](#💡 并发编程进阶(11题))
- [💎 下一步的学习建议](#💎 下一步的学习建议)
-
这里为你准备了50道聚焦于Java新特性和深度原理的高质量基础问题,帮助你构建更完整的知识体系。
为了让您直观了解这50道题的侧重点分布,我将其按照考察维度进行了分类:
22% 18% 18% 20% 22% 50道Java基础问题分类 Java新特性与语法 面向对象与设计 核心API与特性 JVM与性能基础 并发编程进阶
💡 Java新特性与现代语法(11题)
这部分考察你对现代Java开发效率工具和编程范式的理解。
-
Java 8的Stream API中,
map()和flatMap()有什么区别?- 核心分析 :
map()将元素1:1映射为另一个元素,flatMap()将每个元素映射为一个Stream,然后将所有Stream扁平化 连接成一个Stream。例如,将List<List<String>>转为List<String>必须用flatMap()。
- 核心分析 :
-
Optional类是用来解决什么问题的?滥用Optional可能会带来什么坏处?- 核心分析 :旨在更优雅地处理
null,避免NPE。滥用如:在类字段、方法参数、集合中使用Optional会增加复杂度;毫无必要的链式调用(.get().get())违背其设计初衷。它应主要用于方法返回值。
- 核心分析 :旨在更优雅地处理
-
Java 9的模块化(Project Jigsaw)中,
requires、exports、opens指令分别是什么作用?- 核心分析 :
requires声明模块依赖;exports将指定包导出给所有 模块;opens为了反射(如Spring框架)开放包,可以开放给所有模块(opens pkg)或指定模块(opens pkg to module)。
- 核心分析 :
-
接口的私有方法(Java 9)和静态私有方法(Java 8/9)有什么用处?
- 核心分析 :都是为了在接口内封装和复用公共代码。私有方法供接口内默认方法或其他私有方法调用,实现了接口内部实现的隐藏和重构,提高了代码的内聚性。
-
局部变量类型推断(
var,Java 10)可以在哪些场景使用?有哪些限制?- 核心分析 :可用于局部变量初始化时、
for循环索引、try-with-resources中。限制 :不能用于方法参数、返回值、字段、catch参数、lambda表达式参数(编译器无法推断)等。它不是动态类型,编译期即确定。
- 核心分析 :可用于局部变量初始化时、
-
switch表达式(Java 14成为标准)相比传统switch语句有哪些改进?- 核心分析 :① 可直接返回值(使用
->箭头语法或yield关键字)。② 不需要break防止穿透。③ 可作为表达式嵌入其他语句。④ 覆盖所有可能值(穷举性)时不需要default。更安全、简洁。
- 核心分析 :① 可直接返回值(使用
-
文本块(Text Blocks,Java 15)有什么好处?如何表示?
- 核心分析 :方便处理多行字符串(如JSON、SQL、HTML)。使用三引号
"""作为定界符。会自动删除结尾 的公共缩进(以最短的非空行为基准),但开头换行符需注意处理。
- 核心分析 :方便处理多行字符串(如JSON、SQL、HTML)。使用三引号
-
Record类(Java 16)是什么?它和Lombok的
@Data注解有何异同?- 核心分析 :Record是不可变数据 的透明载体,编译器自动生成
final字段、全参构造器、equals()、hashCode()、toString()等方法。与Lombok相比,它是语言特性,无需额外依赖和注解处理器,但功能更固定(不可变),定制能力较弱。
- 核心分析 :Record是不可变数据 的透明载体,编译器自动生成
-
Sealed类(密封类,Java 17)解决了什么问题?如何声明?- 核心分析 :用于限制一个类或接口只能被指定的子类继承或实现 ,在建模领域时提供更精确的层次控制。声明:
public sealed class Shape permits Circle, Square, Rectangle {...}。子类必须是final、sealed或non-sealed。
- 核心分析 :用于限制一个类或接口只能被指定的子类继承或实现 ,在建模领域时提供更精确的层次控制。声明:
-
instanceof模式匹配(Java 16)如何简化代码?- 核心分析 :传统
instanceof需要检查后显式强转。新模式:if (obj instanceof String s && s.length() > 5),在判断成功的同时,将obj自动转换为String并赋给变量s,省去了一行转换代码。
- 核心分析 :传统
-
什么是
var关键字与菱形运算符的组合类型推断增强(Java 11)?- 核心分析 :Java 11允许在Lambda表达式的形参中使用
var,并与注解结合,如:(@NotNull var s) -> s.length()。这使得Lambda参数可以添加注解,增强了表达力。
- 核心分析 :Java 11允许在Lambda表达式的形参中使用
💡 面向对象与设计进阶(9题)
这部分考察对设计原则、模式和复杂场景的把握。
-
什么是"组合优于继承"(Composition over Inheritance)原则?其优势是什么?
- 核心分析 :通过包含(持有引用)而非扩展(继承)来复用功能。优势:① 更灵活 :运行时可以改变行为。② 更安全 :不会暴露父类内部细节。③ 避免继承层次爆炸 和脆弱的基类问题(父类修改可能破坏所有子类)。
-
如何实现一个不可变对象(Immutable Object)?需要特别注意什么?
- 核心分析 :① 类
final;② 字段private final;③ 无setter;④ 通过构造器深拷贝传入的可变对象;⑤getter返回可变对象的副本。特别注意:如果字段是集合,应使用Collections.unmodifiableList()包装或返回新副本。
- 核心分析 :① 类
-
什么是建造者模式(Builder Pattern)?在什么场景下使用它比构造器或
setter更好?- 核心分析 :将复杂对象的构造与其表示分离。适用于:① 对象有很多可选参数,且构造逻辑复杂。② 希望对象一旦创建就不可变(
Immutable)。相比重叠构造器(telescoping constructor)更易读,相比setter能保证对象创建后的状态一致性。
- 核心分析 :将复杂对象的构造与其表示分离。适用于:① 对象有很多可选参数,且构造逻辑复杂。② 希望对象一旦创建就不可变(
-
@Override注解的作用是什么?为什么推荐总是使用它?- 核心分析 :① 编译器检查 :确保该方法确实重写了父类方法,避免因拼写或签名错误导致的意外重载。② 提高可读性:明确标识出这是重写方法。这是一个低成本高收益的最佳实践。
-
什么时候该用抽象类?什么时候该用接口?从Java 8开始这个选择有变化吗?
- 核心分析 :本质区别 是"是什么"(抽象类,is-a)和"能做什么"(接口,has-a/behaves-like)。Java 8后接口可以有默认实现,使得接口在行为复用 上能力增强,但状态(字段)仍只能由抽象类定义。优先考虑接口,除非需要定义非静态、非常量字段。
-
什么是
回调(Callback)机制?在Java中通常如何实现?- 核心分析 :一种"你完成后通知我"的异步模式。实现方式:① 接口回调 (经典,如事件监听)。② Lambda/方法引用 (Java 8+,简洁)。③ Future
/CompletableFuture` (更现代的异步回调)。
- 核心分析 :一种"你完成后通知我"的异步模式。实现方式:① 接口回调 (经典,如事件监听)。② Lambda/方法引用 (Java 8+,简洁)。③ Future
-
为什么说
Object.clone()方法是"有问题"的?更好的替代方案是什么?- 核心分析 :① 是
protected方法,需重写为public。② 浅拷贝,需手动实现深拷贝。③ 不调用构造器,破坏对象初始化逻辑。替代:复制构造器 (new MyClass(original))或复制工厂方法(更灵活、清晰)。
- 核心分析 :① 是
-
什么是
空对象模式(Null Object Pattern)?它如何帮助消除null检查?- 核心分析 :提供一个实现接口、但行为为空或返回默认值的特殊对象,代替
null引用。这样客户端代码可以无差别地调用方法,无需判空,代码更流畅。例如,返回空集合Collections.emptyList()而非null。
- 核心分析 :提供一个实现接口、但行为为空或返回默认值的特殊对象,代替
-
如何防止一个类被实例化?(至少说出三种方式)
- 核心分析 :① 将类声明为抽象类 (但仍可被继承实例化)。② 提供私有构造器 (最常用,工具类标准做法)。③ 在构造器中抛出异常 (如
AssertionError)。④ 使用枚举单例(如果是单例需求)。
- 核心分析 :① 将类声明为抽象类 (但仍可被继承实例化)。② 提供私有构造器 (最常用,工具类标准做法)。③ 在构造器中抛出异常 (如
💡 核心API与高级特性(9题)
这部分深入语言核心机制和API的设计细节。
-
Class.forName()和ClassLoader.loadClass()在加载类时有什么区别?- 核心分析 :
Class.forName(className)默认会初始化 类(执行静态块);ClassLoader.loadClass(className)默认只加载 类,不初始化。Class.forName可以指定是否初始化,且使用调用者的类加载器。
- 核心分析 :
-
Java的动态代理(Dynamic Proxy)和CGLIB代理有什么区别?- 核心分析 :JDK动态代理 :基于接口 ,使用
Proxy和InvocationHandler,生成实现接口的代理类。CGLIB代理 :基于继承,生成目标类的子类作为代理类,因此可以代理无接口的类。Spring AOP默认策略:有接口用JDK,无接口用CGLIB。
- 核心分析 :JDK动态代理 :基于接口 ,使用
-
什么是
服务加载器(ServiceLoader)?它如何工作?- 核心分析 :SPI机制的核心类。在
META-INF/services/目录下,以接口全限定名 为文件名,文件内容为实现类全限定名 (每行一个)。ServiceLoader.load(Interface.class)会加载并实例化所有配置的实现类。
- 核心分析 :SPI机制的核心类。在
-
Comparator接口的comparing()、thenComparing()方法有什么用?它是如何实现链式比较的?- 核心分析 :用于构建复杂的比较器。
comparing(Function keyExtractor)提取主排序键;thenComparing()用于在主键相等时指定次要排序键。这些方法返回一个新的Comparator,支持流畅的链式调用,极大简化了多字段排序。
- 核心分析 :用于构建复杂的比较器。
-
java.time.Instant和java.time.LocalDateTime有什么区别?- 核心分析 :
Instant是时间线上的一个瞬时点 (与GMT/UTC的偏移),绝对时间 ,适合机器时间戳、日志。LocalDateTime是不带时区的本地日期时间 ,是挂钟时间,适合表示计划、生日等需要人类阅读但无需时区的情景。
- 核心分析 :
-
如何安全地将一个
受检异常(Checked Exception)在Stream或Lambda中抛出?- 核心分析 :Lambda不允许直接抛出受检异常。解决方法:① 在Lambda内部
try-catch并包装为RuntimeException。② 使用一个包装函数,在函数内部处理异常。③ 使用如ThrowingFunction这样的第三方工具接口。
- 核心分析 :Lambda不允许直接抛出受检异常。解决方法:① 在Lambda内部
-
Files工具类中,readAllLines()和lines()方法有什么区别?- 核心分析 :
readAllLines()一次性将所有行读入内存的List。lines()返回一个Stream<String>,惰性加载 ,适合处理大文件,不会一次性占用大量内存,可以用filter、map等流操作处理。
- 核心分析 :
-
什么是
try-with-resources语句的"抑制异常(Suppressed Exceptions)"?- 核心分析 :当
try块和close()方法都抛出异常时,try块的异常被抛出,close()的异常会被抑制 (添加到主异常的suppressed数组),可以通过Throwable.getSuppressed()获取。这确保了最终抛出的是最有意义的业务异常。
- 核心分析 :当
-
@SafeVarargs注解的作用是什么?在什么情况下需要使用它?- 核心分析 :用于抑制在不可具体化的参数(如泛型可变参数)上可能出现的"堆污染"警告。它告诉编译器,该方法会安全地处理可变参数,不会进行不安全的操作(如将错误类型的元素存入数组)。开发者需自己确保安全。
💡 JVM与性能基础(10题)
这部分考察程序运行时的底层机制和性能意识。
-
除了
-Xms和-Xmx,你还知道哪些常用的JVM参数?- 核心分析 :
-XX:NewRatio(新生代老年代比例)、-XX:SurvivorRatio(Eden和Survivor区比例)、-XX:+UseG1GC(指定收集器)、-XX:MaxMetaspaceSize(元空间最大值)、-XX:+HeapDumpOnOutOfMemoryError(OOM时生成堆转储)、-XX:OnOutOfMemoryError(OOM时执行脚本)。
- 核心分析 :
-
什么是
逃逸分析(Escape Analysis)?JVM基于它可以做什么优化?- 核心分析 :分析对象动态作用域,判断对象是否"逃逸"出方法或线程。基于此可做:栈上分配 (对象在栈上创建销毁,减轻GC压力)、锁消除 (对线程局部对象移除同步锁)、标量替换(将对象分解为基本类型在栈上分配)。这些是JIT的优化手段。
-
JDK自带的JVM监控和故障处理工具有哪些?你用过哪些?- 核心分析 :
jps(进程状态)、jstat(统计信息)、jmap(内存映像)、jstack(线程快照)、jinfo(配置信息)、jcmd(多功能命令)、jconsole、VisualVM、JMC(Java Mission Control)。jstack查死锁、jmap+MAT分析内存泄漏是常用组合。
- 核心分析 :
-
什么是
类加载器泄漏(ClassLoader Leak)?通常是如何引起的?- 核心分析 :当自定义
ClassLoader加载的类被应用全局缓存(如静态Map)引用,导致ClassLoader及其加载的所有类无法被回收。典型场景:应用服务器(如Tomcat)热部署时,旧应用未完全卸载,内存逐渐累积,引发PermGen/MetaspaceOOM。
- 核心分析 :当自定义
-
System.arraycopy()和Arrays.copyOf(),以及对象.clone()在复制数组时性能上有何差异?- 核心分析 :
System.arraycopy()是本地方法 ,效率最高。Arrays.copyOf()内部调用arraycopy,并多了创建新数组的步骤。clone()对于数组是浅拷贝 ,性能与arraycopy相当。对于大批量复制,arraycopy是最佳选择。
- 核心分析 :
-
解释一下
JIT编译优化中的"内联(Inlining)"是什么?有什么好处?- 核心分析 :将方法调用处替换为方法体本身。好处:① 消除调用开销 (压栈、跳转等)。② 为其他优化创造更多机会 (如常量传播、死代码消除)。是JIT最重要优化之一,但对方法大小有限制(基于
-XX:MaxInlineSize等参数)。
- 核心分析 :将方法调用处替换为方法体本身。好处:① 消除调用开销 (压栈、跳转等)。② 为其他优化创造更多机会 (如常量传播、死代码消除)。是JIT最重要优化之一,但对方法大小有限制(基于
-
什么是
卡表(Card Table)?它在垃圾收集中起什么作用?- 核心分析 :用于解决跨代引用 的GC问题。将老年代划分为大小为512字节的"卡",维护一个卡表。当老年代对象引用新生代对象时,JVM将对应卡标记为"脏"。Minor GC时,只需扫描脏卡,而非整个老年代,大大加快了Young GC的速度。
-
-XX:+DisableExplicitGC参数有什么作用?为什么生产环境有时会启用它?- 核心分析 :禁用
System.gc()调用。生产环境启用是因为:①System.gc()会触发Full GC,造成不可预测的停顿。② 一些框架/库(如NIO的Direct Buffer清理)或RMI会隐式调用System.gc()。启用后需确保应用不依赖显式GC来管理内存(如堆外内存)。
- 核心分析 :禁用
-
如何理解
元空间(Metaspace)和永久代(PermGen)的区别?- 核心分析 :永久代 :在堆内,大小固定易OOM,存储类信息、字符串常量池等。元空间 :在本地内存(Native Memory)中,默认无上限(受系统内存限制),动态调整,将字符串常量池移至堆中。元空间减少了Full GC触发,降低了OOM风险。
-
什么是
偏向锁(Biased Locking)?为什么Java 15开始默认禁用?- 核心分析 :假设锁总由同一线程获得,消除该线程后续的同步开销。但在高竞争或持有锁时间短的场景下,撤销偏向锁(需要安全点)的代价可能超过收益,且增加了JVM复杂性。随着并发模式和硬件变化,其收益已不明显,故被禁用。
💡 并发编程进阶(11题)
这部分深入到并发工具的实现原理和正确使用模式。
-
ThreadLocal的内存泄漏根本原因是什么?为什么WeakReference不能完全解决问题?- 核心分析 :
ThreadLocalMap中Entry继承WeakReference,其key(ThreadLocal对象)是弱引用,但value是强引用 。当key被GC回收变为null时,value仍被Entry强引用,且线程不结束就无法访问和清除这些Entry。必须调用remove()。
- 核心分析 :
-
什么是
happens-before原则?它和as-if-serial语义有什么关系?- 核心分析 :
happens-before是Java内存模型(JMM)定义的内存可见性规则 ,保证前一个操作的结果对后一个操作可见。as-if-serial是单线程内的语义,保证单线程内执行结果不被重排序改变。happens-before是as-if-serial在多线程下的扩展和保证。
- 核心分析 :
-
ConcurrentHashMap的size()方法返回值是精确的吗?为什么?- 核心分析 :不是绝对精确 。它采用分片计数 ,在并发更新时,为了性能,不会全局加锁来获取精确值,而是尝试无锁地累加各段的修改计数。在累加过程中可能有其他线程在修改,所以返回的是一个估计值,但大多数情况下足够用。
-
ForkJoinPool和普通的ThreadPoolExecutor有什么区别?适合什么场景?- 核心分析 :
ForkJoinPool使用工作窃取(Work-Stealing)算法 ,适合处理大量可分解的递归型任务(如计算密集型) 。ThreadPoolExecutor适合处理相互独立的任务(如I/O密集型) 。ForkJoinPool能更好地平衡线程负载。
- 核心分析 :
-
CompletableFuture和Future有什么区别?它如何实现回调?- 核心分析 :
Future获取结果需要阻塞get()。CompletableFuture实现了CompletionStage,支持异步回调 (thenApply/thenAccept)、组合 (thenCompose/thenCombine)、异常处理 (exceptionally/handle)等,是功能强大的异步编程工具。
- 核心分析 :
-
什么是
ABA问题?AtomicStampedReference是如何解决的?- 核心分析 :CAS操作中,变量值从A变B再变回A,CAS会误认为没变。解决方法:
AtomicStampedReference不仅比较值,还比较一个版本戳(Stamp),每次修改版本戳递增,从而避免了ABA问题。
- 核心分析 :CAS操作中,变量值从A变B再变回A,CAS会误认为没变。解决方法:
-
ReadWriteLock(读写锁)的锁降级是什么?为什么允许锁降级,而不允许锁升级?- 核心分析 :锁降级 :持有写锁 -> 获取读锁 -> 释放写锁。这个过程允许了数据在修改后,仍能被当前线程读取,且不会让其他写线程介入,保证了数据一致性。锁升级 (读锁 -> 写锁)容易引起死锁(多个读线程都想升级为写锁时相互等待),故通常不支持。
-
BlockingQueue接口有哪些实现?ArrayBlockingQueue和LinkedBlockingQueue有何区别?- 核心分析 :常见实现:
ArrayBlockingQueue(有界数组)、LinkedBlockingQueue(可选有界的链表)、PriorityBlockingQueue(优先级)、SynchronousQueue(不存储元素)。区别:① 数据结构 :数组 vs 链表。② 锁 :Array一把锁;Linked两把锁(putLock, takeLock),吞吐量通常更高。
- 核心分析 :常见实现:
-
Phaser和CyclicBarrier/CountDownLatch有什么区别?- 核心分析 :
Phaser更灵活强大。① 动态调整 :可动态注册/注销参与方。② 多阶段 :可重复使用,每阶段可不同屏障动作。③ 分层 :支持树形结构降低竞争。CyclicBarrier固定参与方,可重复;CountDownLatch一次性的减数器。
- 核心分析 :
-
什么是
线程饥饿(Thread Starvation)?在Java中哪些情况可能导致?- 核心分析 :低优先级线程长期得不到执行。原因:① 高优先级线程抢占。② 同步区过长或等待条件不满足,使线程长期等待。③ 使用非公平锁,新来的线程可能"插队"成功,导致等待队列中线程饥饿。
-
从
Java内存模型角度,解释final域的重排序规则及其能保证的"初始化安全性"。- 核心分析 :
final域的重排序规则:① 在构造器内对final域的写入,与随后将构造对象的引用赋值给变量,这两个操作不能重排序 。② 初次读包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序 。这保证了:只要对象正确构造(引用不逃逸),其他线程看到的final域一定是构造器初始化后的值,无需同步。
- 核心分析 :
💎 下一步的学习建议
你已经积累了超过100个深度问题。为了将这些知识转化为真正的能力,我建议:
- 构建知识网络图:用思维导图工具,将JVM、并发、集合、新特性等模块的关键概念、类、原理、参数关联起来,形成你的"知识大脑"。
- 场景化学习 :针对"高性能缓存"、"百万级日志处理"、"秒杀系统"等具体场景,思考如何综合运用上述知识(如用
ConcurrentHashMap+Future做缓存、用ForkJoinPool处理日志、用Phaser控制秒杀流程)来设计方案。 - 追踪源头 :对于核心机制(如AQS、
ForkJoinPool工作窃取),直接阅读JDK源码中的关键注释和实现,这是最高质量的一手资料。
如果你希望针对 "微服务架构"、"系统设计"或"特定中间件(如Kafka、Redis)的原理与Java集成" 进行深入探讨,我可以为你提供新的学习路径和问题清单。