简介:面向Java SE 17开发者认证(Exam 1Z0-829)备考人群的实战练习册电子版,由Jeanne Boyarsky与Scott Selikoff编写,Wiley出版,适合已具备一定Java基础、希望系统刷题并查漏补缺的中高级开发者。内容围绕考试可能涉及的主题与概念组织模拟测试题,并配有解析与复习策略,可用于考前自测、定位薄弱模块和安排冲刺节奏。资源包共1个PDF文件,大小约20.96MB,单册结构完整,便于在电脑或平板上翻阅、检索与打印练习。目前已有268人学习下载,具有一定参考热度。借助成体系的练习题与答案解析,读者可模拟真实考试节奏,逐项核对知识盲区,并结合复习建议调整学习计划,提升应试准备的针对性。
1. 一张知识地图:Java SE 17 认证考的是一张网,不是一堆点
Java SE 17 认证考试的备考圈里有个共识:单点知识大家都会,难的是它从哪个角落问你。这份实战练习把语言基础、面向对象、异常、集合与泛型、函数式与 Stream、并发、模块化、I/O 拧成一张网,题目看着眼熟,选项一摆出来就开始玄学。它的价值不在刷题量,而在于按真实考试的分布把知识点摊开,让你看清哪块是主干、哪块只是边角。适合已经写过一阵子 Java、想用模拟测试做最后校准的人;零基础直接上来刷,很容易被大量「看起来都对」的选项拖进踩坑循环。下面这份梳理,就是把这套练习背后的知识地图一层层拆开,再落成能直接抄的复习策略。
2. 语言地基与面向对象:从类型系统到 record、sealed 的复习重点
语言层是整张卷子的最低层,考的不是「知不知道」,而是「边界清不清楚」。基本类型的宽度、装箱缓存、字面量后缀这些点,平时写业务代码几乎不会碰到,考试却专挑这些缝隙出题。面向对象部分更典型:继承、接口、多态谁都会用,但重写规则、构造器链、静态分派与动态分派的差别,一旦放进选项里就能放倒一片。
2.1 基本类型、自动装箱与运算符的边界
八种基本类型要先背成条件反射。整数字面量默认是int,写long必须带L;浮点字面量默认double,要float必须带f。byte、short、char参与算术运算时会自动提升为int,所以byte b = 1; b = b + 1;编译不过,得写成复合赋值b += 1,因为复合赋值隐含了强制转换。这个点在模拟测试里出现频率极高,属于典型的「一眼看过去没问题」的陷阱。
| 类型 | 位宽 | 默认值 | 字面量与运算要点 |
|---|---|---|---|
| byte | 8 | 0 | 范围 -128~127,运算时提升为 int |
| short | 16 | 0 | 同上,复合赋值才做隐式窄化 |
| int | 32 | 0 | 整数字面量默认类型,支持 0b/0/0x 前缀 |
| long | 64 | 0L | 字面量必须带 L 或 l,超过 int 范围不带 L 直接编译错误 |
| float | 32 | 0.0f | 字面量必须带 f,否则 double 不能赋给 float |
| double | 64 | 0.0d | 浮点默认类型,可用科学计数法 |
| char | 16 | '\u0000' | 无符号,可参与算术,char c = 65合法 |
| boolean | 未定义 | false | 不能用 0/1 替代,不能与其他类型互转 |
自动装箱有个经典缓存区间:Integer在 -128 到 127 之间复用同一批对象,超出这个范围每次装箱都是新对象。所以两个值相等的Integer用==比较,可能真可能假,考试就爱在这里埋一个127和一个128做对照。字符串更直接,字面量进常量池,new String()一定在堆上,intern()才会回池子里找。
Integer a = 127, b = 127; Integer c = 128, d = 128; System.out.println(a == b); // true,命中缓存区间 System.out.println(c == d); // false,超出缓存,两个不同对象 System.out.println(c.equals(d)); // true,equals 比较的是值 String s1 = "java"; String s2 = new String("java"); System.out.println(s1 == s2); // false,一个在池里一个在堆上 System.out.println(s1 == s2.intern()); // true,intern 返回池中引用这段代码的关键参数是缓存上下界-128和127,由IntegerCache决定,Short、Byte、Character也有类似机制,Long和Double没有。判断时先问自己三件事:比较的是基本类型还是包装类型、值有没有落在缓存区间、是不是同一个引用。var也是常考点,它只能用于局部变量且必须初始化,不能声明成员变量,不能赋null,不能用于 lambda 参数的类型推断之外的场景。
2.2 继承、接口与多态的判定规则
重写的五条约束要能默写:方法名和参数列表必须一致;返回类型可以是子类型(协变返回);访问权限不能比父类更严格;不能抛出比父类更宽的检查型异常;final、static、private方法不能被重写。重载和重写的区分靠一句话:编译期看引用类型决定调哪个签名,运行期看实际对象决定执行哪个实现。
class Animal { void speak() { System.out.println("animal"); } } class Dog extends Animal { @Override void speak() { System.out.println("dog"); } void speak(int times) { System.out.println("dog x" + times); } } Animal a = new Dog(); a.speak(); // 输出 dog,运行期绑定到 Dog 的实现 // a.speak(3); // 编译错误,Animal 类型上没有这个重载这段的说明点在于:a的静态类型是Animal,所以只能调用Animal里声明过的方法签名;但方法体走的是Dog的版本。重载在编译期就定死了,重写在运行期才定,这是两条完全不同的分派路径。接口部分要记牢:接口字段隐式public static final,接口方法可以是abstract、default、static、private;抽象类可以有构造器和实例字段,接口不能有实例字段。一个类只能继承一个类,但可以实现多个接口,出现 default 方法冲突时必须显式用InterfaceName.super.method()指定。
构造器链也是高频点:this()和super()只能出现一个,且必须是构造器的第一条语句;父类没有无参构造器时,子类必须显式调用带参的super(...)。final有三处含义要分清:修饰类表示不可继承,修饰方法表示不可重写,修饰变量表示只能赋值一次(引用类型是引用不能换,对象内部状态仍可变)。
2.3 record、sealed、enum 三个新语法的考点密度
record是 Java 16 转正、17 成为长期支持的语法,考法集中在它「自动生成了什么」和「不能做什么」。声明record Point(int x, int y) {}之后,编译器自动生成私有 final 字段、规范构造器、按组件顺序的equals/hashCode/toString,类本身隐式final且已隐式继承java.lang.Record,所以不能再extends别的类,但可以实现接口。可以写紧凑构造器做参数校验,不能声明额外的实例字段,静态字段和静态方法可以写。
public record Range(int low, int high) { public Range { // 紧凑构造器,不写参数列表 if (low > high) { throw new IllegalArgumentException("low > high"); } } public int span() { return high - low; } // 可以加实例方法 }紧凑构造器的参数列表为空,赋值的代码由编译器在末尾自动补上,所以校验逻辑写在最前面即可。如果显式写了规范构造器,就不能再写紧凑构造器。sealed用来限定谁能继承:sealed interface Shape permits Circle, Square {},子类型必须声明为final、sealed或non-sealed三者之一,且必须与父类型在同一模块或同一包内。record天然 final,所以经常和sealed搭配做代数数据类型。
enum的考点集中在三处:构造器隐式 private,不能直接new;每个枚举常量可以有自己的方法实现(常量后面跟{ ... });values()、valueOf()、name()、ordinal()的语义区别。枚举可以实现接口,可以被switch匹配,ordinal()依赖声明顺序,业务判断里尽量别用它。
2.4 把 OOP 判定压缩成一张对照表
考场上没时间推导,最稳的办法是把容易混的成对概念做成表,看到题先归类再判断。
| 对照项 | 左侧要点 | 右侧要点 |
|---|---|---|
| 抽象类 vs 接口 | 可有构造器、实例字段、普通方法 | 只能有常量字段,方法默认 public abstract |
| 重载 vs 重写 | 编译期决定,参数列表必须不同 | 运行期决定,签名必须相同 |
| record vs 普通类 | 隐式 final、组件即字段、自动生成 equals | 可自由定义字段与继承关系 |
| 枚举 vs 常量类 | 实例数量编译期固定,天然单例 | 实例可任意创建,需自己控制 |
把这张表默写出来,再回头做这一章的模拟题,错误率会明显下降。真正容易出问题的地方,是题目把两个对照项叠在一起考,比如「一个 sealed 接口被 record 实现,record 里又写了紧凑构造器抛异常」,这时候按「先看类型关系、再看语法约束、最后看运行结果」的顺序拆,比凭印象选要稳。
3. 异常、集合与泛型:最容易被“看起来对”骗过去的三块
这三块有个共同特征:概念都懂,细节全漏。异常考的是传播路径和资源关闭顺序,集合考的是接口契约与实现差异,泛型考的是编译期检查和类型擦除之间的落差。复习时不要停留在「知道有这个类」,而要能说出「这个方法在什么情况下抛哪个异常、调用后集合变成什么样」。实战练习里大量错题都集中在这三块,原因不是不会,而是判断依据不唯一。
3.1 异常体系与 try-with-resources 的执行顺序
顶层是Throwable,往下分Error和Exception。Exception下面再分两支:RuntimeException及其子类是非检查型,其余是检查型。Error一般不捕获,考试里出现OutOfMemoryError、StackOverflowError时基本是让你判断「不该 catch」。catch的顺序必须从子类到父类,否则子类分支不可达,编译直接报错。多重捕获catch (IOException | SQLException e)里的变量隐式 final,不能重新赋值。
finally几乎一定执行,唯一例外是System.exit()或 JVM 崩溃。如果try和finally都抛异常,finally的异常会覆盖try的,原始异常丢失。try-with-resources里资源必须实现AutoCloseable,关闭顺序与声明顺序相反,后声明的先关;如果关闭时抛异常,会被作为抑制异常挂在主异常上,用getSuppressed()取。
try (var in = new FileInputStream("a.txt"); var out = new FileOutputStream("b.txt")) { // 先关 out,再关 in out.write(in.readAllBytes()); } catch (IOException e) { for (Throwable t : e.getSuppressed()) { // 关闭阶段抛出的异常在这里 System.err.println("suppressed: " + t); } }这段的关键在于两个资源的关闭顺序是out先、in后,和声明顺序相反;getSuppressed()返回的是关闭阶段被压制的异常数组,可能为空。自定义异常要注意:继承Exception是检查型,继承RuntimeException是非检查型;重写方法时不能抛出比父类更宽的检查型异常,但可以减少或删除。还有一点常被忽略:catch块里throw e时,如果e是 final 或 effectively final,编译器会做精确重抛分析,方法签名上的throws可以更窄。
3.2 集合接口契约与实现差异
集合部分先记接口契约,再记实现差异。List有序可重复,Set不可重复,Map键不可重复,Queue按队列语义进出,Deque双端。ArrayList随机访问快、中间插入慢;LinkedList插入删除快、随机访问要遍历。HashSet基于哈希,不保证顺序;LinkedHashSet保留插入顺序;TreeSet按自然顺序或比较器排序,元素必须可比较。HashMap允许一个 null 键和多个 null 值,TreeMap不允许 null 键,Hashtable两者都不允许。
| 操作 | ArrayList | LinkedList | HashSet | TreeSet |
|---|---|---|---|---|
| 尾部追加 | O(1) 摊销 | O(1) | O(1) 平均 | O(log n) |
| 随机访问 | O(1) | O(n) | 不支持索引 | 不支持索引 |
| 是否有序 | 插入顺序 | 插入顺序 | 无序 | 排序顺序 |
| null 元素 | 允许 | 允许 | 允许一个 | 不允许 |
equals和hashCode的契约必须一起重写:两个对象equals为真,hashCode必须相等;hashCode相等,equals不一定为真。只重写其中一个,放进HashSet或作为HashMap的键就会出现「明明放进去了却取不出来」的情况。遍历时修改集合(除了通过迭代器自己的remove())会抛ConcurrentModificationException,这是 fail-fast 机制,不是并发问题。
Arrays.asList()返回的是固定长度的列表,底层是数组,add/remove会抛UnsupportedOperationException,但set可以。List.of()返回完全不可变列表,任何修改都抛异常,且不接受 null 元素。还有一个血泪经验:List<Integer>上调remove(1)删的是索引 1,调remove(Integer.valueOf(1))删的才是值为 1 的元素,重载解析在编译期就定死了,这个坑在模拟题里出现得非常频繁。
3.3 泛型:通配符、PECS 与类型擦除
泛型是编译期检查,运行期擦除。擦除带来几个直接后果:不能new T[],不能instanceof List<String>,不能重载仅泛型参数不同的方法,静态成员不能使用类的类型参数。原始类型(raw type)能编译但会产生警告,考试里出现 raw type 基本是在提示你「这里有问题」。
通配符记 PECS:Producer Extends,Consumer Super。List<? extends Number>是生产者,只能读不能写(除了写 null);List<? super Integer>是消费者,只能写不能读成具体类型(读出来是 Object)。
void copy(List<? extends Number> src, List<? super Number> dst) { for (Number n : src) { // extends 侧可以安全读取,元素至少是 Number dst.add(n); // super 侧可以安全写入 Number 及其子类 } } // copy(List<Integer>, List<Object>) 合法 // copy(List<Object>, List<Integer>) 非法,方向反了参数说明:src用extends保证读出来能当Number用,dst用super保证能塞进Number。如果两边都用extends,写入编译不过;都用super,读取只能拿到Object。泛型方法要在返回类型前声明类型参数,比如static <T extends Comparable<T>> T max(List<T> list),这里的T同时约束了入参和返回值。类型推断在链式调用里容易断,必要时用Collections.<String>emptyList()显式指定。
4. 函数式与 Stream:lambda、方法引用、Optional 的复习重点
函数式这一块,考试不考你会不会写,考你写出来的东西「合不合法、什么时候执行、执行几次」。lambda 的语法糖下面藏着函数式接口、目标类型推断和 effectively final 三条硬约束;Stream 的惰性求值决定了中间操作写多少行都不一定执行;Optional 则是最容易被滥用成「包装一层就完事」的工具。复习顺序建议是:先把函数式接口的签名背熟,再理解管道的生命周期,最后才去记 Collectors 的各种方法名。
4.1 函数式接口与 lambda 的合法写法
函数式接口就是只有一个抽象方法的接口,@FunctionalInterface只是编译期校验,不写也能用 lambda 实现。内置的四大件要形成条件反射:Supplier<T>无参有返回,Consumer<T>有参无返回,Function<T,R>有参有返回,Predicate<T>有参返回 boolean。它们的 Bi 变体、UnaryOperator(同类型进出)、BinaryOperator(两个同类型进、一个出)也要认得。
lambda 捕获外部变量时,变量必须是 final 或 effectively final,也就是初始化后不再赋值。捕获的是值不是引用,所以想在 lambda 里改外部计数器只能用数组或原子类,这也是考试爱出的判断题。方法引用有四种形态,判断标准是「参数能不能原样传给目标方法」。
Function<String, Integer> f1 = s -> s.length(); // 普通 lambda Function<String, Integer> f2 = String::length; // 实例方法引用,接收者由参数提供 Supplier<List<String>> f3 = ArrayList::new; // 构造器引用 Consumer<String> f4 = System.out::println; // 绑定实例的方法引用四种形态分别是:静态方法引用类::静态方法、绑定实例引用对象::实例方法、未绑定实例引用类::实例方法(第一个参数当接收者)、构造器引用类::new。方法引用不能重载歧义,编译器靠目标类型推断,推断不出来就编译失败。
4.2 Stream 管道的惰性与终止操作
Stream 分三段:创建、中间操作、终止操作。中间操作返回新的 Stream,全部是惰性的,不遇到终止操作一行都不执行。终止操作触发整个管道,执行完流就消费掉了,不能复用。这个「不能复用」在模拟题里经常用「对同一个流调用两次终止操作」来设套。
短路操作要单独记:anyMatch、allMatch、noneMatch、findFirst、findAny、limit遇到满足条件就停,不会遍历完。有状态操作(sorted、distinct、limit、skip)需要缓冲或记录状态,在并行流里代价更高。peek是中间操作,主要给调试用,放在没有终止操作的管道里什么都不会打印。
Stream.of("a", "bb", "ccc", "dd") .filter(s -> { System.out.println("filter " + s); return s.length() > 1; }) .map(String::toUpperCase) .findFirst() .ifPresent(System.out::println); // 只输出到 bb 为止,后面的元素不处理这段的执行顺序是:filter对a返回 false,继续;对bb返回 true,map转成BB,findFirst拿到结果立即短路,ccc和dd根本不会被filter处理。参数说明:findFirst保证有序流的第一个元素,findAny在并行流里不保证是哪个,串行流下两者结果一样但语义不同。基础类型流IntStream、LongStream、DoubleStream有sum()、average()、summaryStatistics()这类专属终止操作,boxed()可以转回对象流。
4.3 Collectors 与 Optional 的常见组合
Collectors的静态方法按用途分组记:转集合用toList、toSet、toMap;分组用groupingBy、partitioningBy;聚合用counting、summingInt、averagingDouble;字符串拼接用joining;下游再处理用mapping、filtering、flatMapping。toMap有两个高频坑:键重复会抛IllegalStateException,值不能为 null 否则抛NullPointerException,需要传 merge 函数或改用groupingBy。
| 写法 | 求值时机 | 典型误用 |
|---|---|---|
orElse(defaultValue) | 无论 Optional 是否为空都求值 | defaultValue 是耗时操作时白算一次 |
orElseGet(supplier) | 仅当为空时求值 | 写成带副作用的 supplier 导致重复执行 |
orElseThrow() | 为空时抛 NoSuchElementException | 把它当异常兜底而不是契约声明 |
ifPresentOrElse(consumer, runnable) | 两支之一必然执行 | 在 consumer 里再套 Optional |
Optional的设计意图是「返回值可能不存在」的显式表达,不是用来替代 null 检查的万能包装。不要把它当字段、当方法参数、放进集合,也不要直接get(),用map、flatMap、filter链式处理。map返回的是Optional<Optional<T>>时要用flatMap摊平,这个区别在选择题里几乎每套题都会出现一次。
5. 易漏点:并发、模块化与 I/O 里最容易翻车的地方
这三块在考试中占比不算最高,但失分率不低,原因是它们离日常业务代码最远,很多人复习时只扫一眼概念就跳过。实战练习做到后半程会发现,错题不是不会,而是「知道有这么回事,但不知道边界在哪」。下面五条按「易漏点 → 为什么漏 → 怎么补」展开,每条都能直接对应到一套模拟题的考点。
5.1 线程池与并发集合的两个漏点
易漏点一:ExecutorService用完不关闭,JVM 不退出。很多人写完任务提交就以为结束了,结果程序挂在那里不动。为什么漏:newFixedThreadPool创建的是非守护线程,核心线程默认一直存活,主线程结束后 JVM 仍在等它们。怎么补:记住shutdown()是「不再接受新任务、已提交的继续跑完」,shutdownNow()是「中断正在跑的、返回等待队列里没执行的任务」,判断线程池是否真正终止用awaitTermination(timeout, unit)配合isTerminated()。考试里常给一个提交完任务直接打印结果的代码,问输出什么,答案往往是「什么都不输出,进程不结束」。
易漏点二:submit吞掉异常,execute才抛出来。用submit提交任务,任务里抛的异常被包在Future里,不调get()就永远看不到;execute提交的任务异常会直接交给UncaughtExceptionHandler。为什么漏:平时用submit多,又很少调get(),异常就被静默吞了,排查时像黑匣子。怎么补:需要结果或需要感知异常就用submit+get(),get()抛的是ExecutionException,原始异常在getCause()里;不需要结果就用execute。并发集合的复合操作要用原子方法,check-then-act写法在ConcurrentHashMap上依然有竞态,改用computeIfAbsent、merge、putIfAbsent。
5.2 模块化与 I/O 的三个漏点
易漏点三:requires transitive、exports ... to、opens三者混用。为什么漏:三个关键字都和「可见性」有关,但作用对象完全不同,做题时只看关键字不看位置。怎么补:requires声明本模块依赖谁;requires transitive表示依赖我的模块也自动读到它;exports把包对外公开;exports ... to只对指定模块公开;opens允许运行期通过反射访问,编译期不导出。考试常问「哪个模块能访问哪个包」,按「先看 requires 链,再看 exports 范围」两步走就不会乱。
易漏点四:模块路径和类路径混用。为什么漏:本地跑示例时两者都能跑通,考试给一段启动命令问行为,就容易凭感觉选。怎么补:模块路径上的具名模块不能被类路径上的代码随意反射访问,未命名模块可以读所有模块但反过来不成立。--add-exports、--add-opens、--add-reads是三个不同的补丁开关,分别对应导出、反射打开、读取依赖,别记串。
易漏点五:Path与Files的语义差别。为什么漏:Paths.get()和Path.of()都能创建路径,resolve和relativize的结果方向容易搞反。怎么补:resolve是拼接,传入绝对路径时直接返回传入的那个;relativize求相对路径,两个路径必须同类型(都是绝对或都是相对)否则抛IllegalArgumentException;normalize()只处理.和..,不检查文件是否存在。Files.copy默认目标存在就抛FileAlreadyExistsException,要覆盖必须加StandardCopyOption.REPLACE_EXISTING;Files.walk返回的是惰性流,用完要关闭,Files.walkFileTree则需要实现FileVisitor的四个回调。序列化方面,transient字段不参与序列化,serialVersionUID不一致会抛InvalidClassException,这些点在判断题里出现得比想象中多。
6. 复习清单:把模拟测试用成查漏补缺的工具
刷题最怕的是「做完对答案,错了看一眼解析,下次还错」。我的做法是把每套模拟测试当成一次体检,而不是一次打分。具体节奏是:第一遍限时做完,只标记不确定的题,不查资料;第二遍只重做标记题,把每个选项为什么错写出来;第三遍按章节归类错题,看错误集中在哪几个模块。三遍下来,真正需要回炉的知识点通常不超过十个。
错题归因建议用一张固定格式的表,逼自己写清「错在哪一层」,而不是笼统写「粗心」:
| 归因类型 | 判断标准 | 对应动作 |
|---|---|---|
| 概念空白 | 解析里的名词没见过 | 回到对应章节补概念,做 5 道同类题 |
| 边界模糊 | 知道概念,但两个选项都像对的 | 写出两者的判定条件,做对比表 |
| 审题失误 | 题目问「不正确」,选了正确的 | 做题时圈出否定词,放慢前 10 题 |
| 执行顺序 | 代码能看懂,输出结果算错 | 手写执行轨迹,逐步标变量值 |
还有几个具体习惯值得强制养成。第一,凡是涉及==和equals的题,先在草稿上标出「基本类型 / 包装类型 / 引用类型」再判断。第二,凡是 Stream 题,先找终止操作在哪,没有终止操作就直接判「不执行」。第三,凡是并发题,先看线程池有没有关闭、异常有没有被Future吞掉。第四,凡是模块化题,先把requires链画出来再谈访问权限。第五,凡是 I/O 题,先确认路径是相对还是绝对、目标文件存不存在。
最后一周不要再开新题,把错题表从头到尾过两遍,每道错题用一句话说出「当时为什么选错、正确依据是什么」,说不出来就说明还没真懂。考试当天遇到卡住的题,先标记跳过,别在一道题上耗超过两分钟——这套卷子的容错率比想象中高,节奏乱掉才是真的翻车。
从那以后我每次做模拟测试,都强制走一遍「限时做完 → 标记不确定 → 写归因表」这三步,再也不敢对完答案就翻篇。希望帮到你。完整的分章练习与解析按文末说明自取即可。
本文还有配套的精品资源,点击获取