面试模块:15 道题,接住下一问
var 是动态类型吗?它到底在什么时候确定类型?
不是。var 是编译期的局部变量类型推断:编译器根据初始化表达式推断出具体类型,之后类型就固定了;字节码里没有 var,局部变量表里也是具体类型。
它只能用于带初始化器的局部变量、for 循环变量、try-with-resources 和 lambda 形参。var 本身是 JDK 10 的 JEP 286;JDK 11 的 JEP 323 只新增了 lambda 形参这一处用法。不能用于字段、方法参数、返回值、catch 形参,也不能写 var x = null 或 var a = 1, b = 2。
var 与 diamond 一起用时只推断到 ArrayList<Object>;匿名类用 var 可以访问额外成员,这是它少数能表达普通写法表达不了的类型。
哪些位置不能用 var?lambda 参数用 var 有什么约束?
字段、方法参数、方法返回值、构造器参数、catch 形参都不能用 var;没有初始化器、初始化为 null、初始化表达式是 void 方法调用时也无法推断。
lambda 参数用 var 时,所有参数要么都写 var,要么都不写;混用不合法。加上 var 后可以给参数加注解,这是它在 lambda 里的主要价值。
var 不改变运行期类型,也不带来装箱/拆箱差异;它只是少写类型名。可读性优先时,显式写类型仍然更好。
JDK 11 的 HttpClient 应该如何管理生命周期?为什么不能 try-with-resources?
HttpClient 是线程安全、长生命周期的门面,内部持有连接池、selector 和 executor。应该复用一个实例,而不是每次请求 new 一个。
JDK 11 的 HttpClient 没有实现 AutoCloseable,没有 close(),不能 try-with-resources;连接池只能靠空闲回收或进程退出释放。这个限制在 JDK 21 才改变。
默认 executor 是内部实现,executor() 返回 Optional;需要控制回调线程时才传自己的 executor。
send 和 sendAsync 的底层管线有区别吗?异常与超时怎么传播?
没有本质区别。两者共用连接池、SelectorManager 和协议 exchange 管线;send 只是同步等待 sendAsync 的结果,差别在调用方是否阻塞。
sendAsync 返回 CompletableFuture,默认在客户端 executor 上完成;thenApply 默认在完成线程执行,需要指定线程池时用 thenApplyAsync(fn, executor)。
异常要沿 cause 链看:get() 抛 ExecutionException,join() 抛 CompletionException,真正的 HttpTimeoutException 在 cause 里。
设置了 HTTP_2,响应就一定是 HTTP/2 吗?连接池的复用单位是什么?
不一定。HTTP_2 是客户端偏好,不是保证;客户端会通过 ALPN(HTTPS)或 h2c 升级(明文)协商,服务端不支持时回落到 HTTP/1.1。实际协议看 response.version()。
HTTP/1.1 复用 TCP 连接,但同一时刻一个连接只有一个在途请求;HTTP/2 在一条连接上复用多个 stream,可以并发承载多个请求。
JDK 自带的 com.sun.net.httpserver.HttpServer 只支持 HTTP/1.1,所以本地示例只能验证连接复用,不能验证多路复用。
isBlank、strip、lines、repeat 各自解决什么问题?和旧 API 有什么差异?
isBlank() 判断空或只含空白;strip() 按 Character.isWhitespace 去掉首尾空白,能处理 Unicode 空白;trim() 只去掉 ≤ U+0020 的字符。
lines() 按 \n、\r、\r\n 切分并返回 Stream,末尾换行不产生额外空行;repeat(n) 返回重复 n 次的字符串,n 为负抛 IllegalArgumentException。
这些方法都在 JDK 11 加入;stripLeading/stripTrailing 是同一批。JDK 10 及以前没有它们。
Files.readString / writeString 的默认编码和异常行为是什么?大文件怎么办?
两者都在 JDK 11 加入,默认 UTF-8。readString 遇到非法字节抛 MalformedInputException;writeString 默认 CREATE、TRUNCATE_EXISTING、WRITE。
小文件用 readString 方便;大文件用 Files.lines() 或 newBufferedReader(),避免整个文件进内存。lines() 返回的 Stream 必须关闭。
Path.of 也是 11 加入的;11 之前用 Paths.get。指定编码时显式传 Charset,不要依赖平台默认值。
List.of、Arrays.asList、unmodifiableList、List.copyOf 有什么区别?
List.of 返回结构不可变的列表,add/remove/set 都抛 UnsupportedOperationException,且拒绝 null;它是 JDK 9 的工厂方法。
Arrays.asList 是固定大小视图,set 允许,底层数组变化会反映;Collections.unmodifiableList 是只读视图,源集合变化会反映;List.copyOf(JDK 10)是不可变快照,拒绝 null。
四者都不是深不可变:元素对象本身可变时,修改元素状态仍会反映到集合里。
Map.of 有哪些数量与重复键限制?为什么不是编译错误?
Map.of 最多 10 对键值,超过用 Map.ofEntries(Map.entry(...), ...);重复键在运行时抛 IllegalArgumentException,null 键或值抛 NullPointerException。
Set.of 的重复元素同样运行时抛 IllegalArgumentException;这些都不是编译错误,因为重复只能在运行期检查。
不可变集合的迭代顺序未指定,不要依赖;需要顺序时显式用 LinkedHashMap 或 TreeMap。
命名模块、自动模块、未命名模块有什么区别?类路径应用为什么读不到模块路径上的类?
命名模块有 module-info,放在模块路径;自动模块是没有 module-info 的普通 jar,放在模块路径后导出所有包、读取所有命名模块;未命名模块是类路径上的所有 jar,读取所有已解析模块。
模块路径上的模块不会自动全部解析。根模块来自 -m 的主模块、--add-modules,以及它们递归 requires 的模块。
类路径应用访问模块路径上的类时,如果那个模块没被解析,就会 NoClassDefFoundError;加 --add-modules 让它成为根模块即可。
exports 和 opens 有什么区别?JDK 11 的非法反射为什么只是警告?
exports 把包的 public 类型暴露给其他模块,影响编译期和运行期的普通访问;opens 允许运行时深反射访问包内所有成员,不影响编译期。
JDK 9–15 默认 --illegal-access=permit,第一次非法反射给警告后放行;JDK 16 默认 deny(JEP 396);JDK 17 移除该选项(JEP 403)。
--add-opens java.base/java.lang=ALL-UNNAMED 可以把包开放给未命名模块;这是运行时补丁,能用公开 API 时不要依赖它。
ZGC 如何做到低暂停?着色指针和读屏障各自解决什么问题?
ZGC 用着色指针把标记状态放进引用本身:4 个颜色位(marked0、marked1、remapped、finalizable),同一份堆内存在三个虚拟视图中映射,染色不需要改地址。
读屏障在每次加载引用后检查颜色:快路径直接使用,慢路径完成标记、跟随转发指针或重映射。标记和重定位的大部分工作与应用线程并发执行,STW 只剩 Mark Start、Mark End、Relocate Start。
JDK 11 的 ZGC 是实验特性,仅 Linux/x64,堆 8MB–4TB,暂停目标 <10ms;单代、无压缩 oops、不支持类卸载。15 转正,21 才有分代。
JDK 11 下 G1、ZGC、Epsilon 应该怎么选?
JDK 11 默认 G1,全平台、有分代、支持类卸载和压缩 oops,绝大多数应用先留在 G1。
ZGC 适合大堆、停顿敏感、能接受读屏障开销的 Linux/x64 服务;11 里它是实验特性,不建议作为默认生产 GC。
Epsilon 是不回收的 no-op GC,只适合性能测试、短生命周期任务或明确知道不会 OOM 的场景;堆满就失败。
升级到 JDK 11 时,最常见的移除项和弃用项有哪些?
JDK 11 移除了 Java EE 和 CORBA 模块(JEP 320):java.xml.bind、java.xml.ws、java.activation、java.xml.ws.annotation、java.corba、java.transaction、java.se.ee。JAXB/JAX-WS 需要引入外部实现。
java.transaction.xa 没有被移除,仍是独立模块;不要把所有 javax 包都当成没了。
Nashorn 在 11 只是弃用(JEP 335),15 才移除;Pack200 在 11 弃用(JEP 336),14 移除;CMS 在 14 移除。升级时不要把弃用当成已移除。
--release 和 -source/-target 有什么本质区别?为什么迁移时优先用 --release?
--release N 用目标版本的 ct.sym 限制可用 API,同时设置 source/target;它能阻止你编译到目标 JDK 不存在的 API,也能阻止依赖 JDK 内部 API。
-source 8 -target 8 只改语言级别和 class 版本,仍然链接当前 JDK 的 API;例如在 JDK 11 上用它们编译 String.isBlank() 会成功,但生成的 class 在 8 上运行会 NoSuchMethodError。
JDK 11 的 javac 支持 --release 6..11;跨版本构建应优先用 --release,而不是 source/target。
怎么刷这套题HOWTO
- 先按主题筛:一次只攻一个主题,避免题目切换导致理解断裂。
- 先自答再展开:用 30 秒说出结论 + 一条原因 + 一个例子。
- 重点看追问:追问模拟面试官的第二层问题,能接住才算真会。
- 最后看版本陷阱:同一 API 在不同 JDK 的状态差异,是最容易答错的点。
- 标记掌握:不确定的不要标记;隔天再复习一遍。
难度说明LEVELS
- 高频:面试中出现频率高,或属于 JDK 11 的核心卖点。
- 进阶:需要理解机制、版本边界或实现取舍,答好能拉开差距。