每年到期末学习群里总会出现同一种消息“Java第十版第九章习题全部答案求一份”。发消息的同学通常处于两种状态作业马上要交而代码还没写完或者选择题对完答案后错了一半开始怀疑手里的答案册有问题。作为从Java SE 5用到现在Java 17的老开发我太清楚这个标题背后的分量了——不管你是不是在用那本经典的第十版教材第九章几乎都是Java学习路上的第一道分水岭。这一章的代码如果是自己一行行敲出来的后面学集合、学框架、学并发都会顺很多如果这章是靠抄答案混过去的面试遇到手写代码的环节前面欠的债会连本带利还回来。1. 先别急着求答案搞清楚你的“第九章”是哪一章1.1 不同教材的第九章题目风格可能完全不一样先说一个扎心的事实Java教材的第十版不止一本第九章甚至不是同一个主题。有些教材把集合放在第九章有些把异常处理放在第九章也有些把图形界面和事件处理放在第九章。我在学习群里见到的最多求助多半来自那本把集合当成核心章节的教材因为集合章节的题目最灵活、代码量最大也是最不适合直接背的一章。教材体系第九章常见主题题目特点经典基础教材集合框架大量编程题、API选型、底层原理简答题工程向教材异常处理逻辑判断题、自定义异常、try/catch改写应用型教材GUI/事件处理界面布局、事件监听、功能联动如果你手上的教材把集合放在第九章也就是最常见的情况那这篇博客的每一节基本都能用上如果你的第九章叫异常处理或图形界面下面讲的“先搭知识地图再自证答案”的学习方法同样成立只要把主线知识点替换成对应的章节内容。1.2 为什么“全部答案”这个需求本身很危险“全部答案”听起来省事实际上会把你拖进三种困境。第一作业交得越顺利期末复习越空虚因为集合章节的知识点根本不是靠背能背下来的。第二网传答案未必是对的很多答案是用老版本JDK写的Java 8、Java 11、Java 17的API行为存在差异直接拿旧答案跑新环境运行结果可能完全不同。第三面试环节最喜欢问集合的底层实现HashMap如何扩容、为什么增强for循环里调用list.remove()会报异常这类问题抄过作业的同学很难答上来。所以我的建议一直很直接答案可以看但必须是自己跑通代码之后用来核对的答案而不是交作业之前用来复制的答案。这篇博客不会罗列一堆题目的标准解而是给你一套自己推导答案、验证答案、排查错误的方法这套方法比任何答案册都抗用。2. 集合章节的知识主线把API背后的机制摸透如果第九章真的是集合你需要做的第一件事就是把整章知识点整理成一张地图。集合章节内容多而杂List、Set、Map各有各的实现类接口层次也深没有主线的话题目稍微一变型就懵。2.1 List、Set、Map三大家族的选型逻辑集合框架可以拆成三块List存有序可重复的数据Set存不重复的数据Map存键值对。表面上只是接口不同实际上每个接口背后都连着不同的数据结构。ArrayList底层是动态数组查询快、插入慢LinkedList底层是双向链表头尾插入删除快、随机访问慢HashSet和HashMap底层是哈希表查找接近常数时间TreeSet和TreeMap底层是红黑树元素天然有序。接口典型实现底层结构适用场景ListArrayList动态数组频繁随机访问、按索引遍历ListLinkedList双向链表频繁头尾插入删除SetHashSet哈希表快速去重、无序存储SetLinkedHashSet哈希表加链表去重且保持插入顺序SetTreeSet红黑树去重且自动排序MapHashMap哈希表按键查询值MapTreeMap红黑树按键有序的映射很多习题会给你一个场景让你选择集合类型。记住一条主线要索引访问选List要快速查重选Set要按键找值选Map要自动排序优先考虑TreeSet或TreeMap。代码题里“统计每个单词出现次数”这类题背后其实就是“Map存映射关系”这一条。2.2 ArrayList扩容和HashMap哈希是简答和选择题的重灾区期末试卷里集合章节的简答题和选择题最爱考两个底层细节ArrayList扩容和HashMap哈希。ArrayList默认容量是10每次扩容大约是原容量的1.5倍。JDK 7到JDK 8之后扩容实现有变化但考察的核心是“为什么需要扩容”和“怎么减少扩容”。如果明确知道数据量就应该通过构造函数指定初始容量避免数据还没放完就多次扩容白白浪费时间。HashMap默认初始容量16加载因子0.75当元素个数超过容量乘以0.75时HashMap会扩容为原来的两倍。Java 8之后当某个桶的链表长度超过8并且数组容量大于等于64时链表会转换成红黑树把最坏情况下的查询复杂度从O(n)降到O(log n)。题目的陷阱往往藏在参数计算里。比如让你估算HashMap存1000个元素需要多大的初始容量按加载因子0.75算1000除以0.75约等于1334而HashMap要求容量是2的幂向上取整就得给2048的初始容量。如果只给1024数据还没放完就会触发多次扩容性能雪崩。2.3 迭代器与并发修改一动手写代码就会踩的坑集合章节的编程题代码写出来不难跑起来报错很常见。最典型的报错是ConcurrentModificationException。直接用增强for遍历list在循环体里调用list.remove()十有八九会抛这个异常。原因很简单迭代器内部维护了一个修改计数器检测到集合被外部修改后就会触发fail-fast机制宁可抛异常也不给你不确定的结果。正确的做法是使用迭代器自己的remove()方法或者在Java 8之后用removeIf()。这里先记住结论遍历中修改集合永远优先考虑迭代器自带的方法而不是集合自己的方法。这个点我在第5节还会专门展开因为它是集合章节编程题里翻车率最高的位置。2.4 三个特别容易记错的判断题集合章节的选择题里有些说法看起来对实际是错的。我平时辅导同学时总结出三个高频易错点。第一“HashSet不允许存放null”。这个说法是错的。HashSet允许放入一个null值因为null的哈希值固定它会落在某个桶里。真正不允许null的是TreeSet因为TreeSet在插入时就需要比较元素大小null无法参与比较运行时会抛NullPointerException。第二“TreeSet可以对任意自定义对象直接排序”。这个说法也是错的。TreeSet要求元素实现Comparable接口或者在创建TreeSet时传入一个Comparator否则插入第二个对象时就会在比较环节抛ClassCastException。做题时想给自定义对象排序要么让类实现Comparable要么单独写Comparator没有第三种偷懒方式。第三“Map的values()返回的是List”。这也是错的。Map.values()返回的是Collection视图不是List它不保证顺序而且对视图的修改会直接反映到原Map。很多同学想通过values()拿到List再去排序结果代码能编译运行结果却不可控本质就是没理解“视图”这个定位。3. 别急着搜答案先掌握三步“自证法”你有没有经历过这种情况题做完了感觉答案是对的但心里没底最后只能去网上搜。其实代码对不对用三个手段就能判断根本不用依赖别人的答案。3.1 用JShell把API行为当场验证Java 9以上自带JShell工具终端里输入jshell回车就能一行一行执行Java代码。比如你想验证ArrayList的remove(int)和remove(Object)到底有什么区别直接在JShell里敲出来结果立刻可见。jshell import java.util.*; var list new ArrayListInteger(); list.add(1); list.add(2); list.add(3); list.remove(1); // 这里删的是下标1对应的元素2 list.remove(Integer.valueOf(1)); // 这里删的是值为1的元素 System.out.println(list);JShell对学习集合尤其友好。集合的API方法多光靠记忆容易混用JShell随手试出来的结果就是最权威的答案。我遇到不确定的API行为时几乎不会去翻旧帖子直接JShell跑一遍几秒钟就出结论。3.2 写测试代码验证题目的边界条件每道编程题都有隐含的边界条件输入为空时怎么办全部元素相同时怎么办数据量很大时会不会超时很多网传答案在普通用例上能跑通一到边界就翻车。你在核对答案之前先想清楚题目的边界再把你自己的解法拿到边界用例上跑一遍。举个例子题目要求“返回去重后的列表”至少需要测四种输入空列表、单个元素列表、全部相同元素的列表、元素顺序被打乱的列表。只测一种用例就认为自己写对了是新手最常见的错觉。测试用例越极端你对自己答案的信心就越足这才是“答案”真正的意义。3.3 用调试器观察集合内部的数据结构IDE的调试器不只是用来查错的它还能让你直观看到ArrayList扩容前后的内部数组长度、HashMap桶里的链表结构。我在学习集合章节时最喜欢在断点处展开变量面板看HashMap的内部数组长度亲眼看着它从16变成32。理解了扩容之后很多题目不用死记也能推出来。遇到题目答案有争议时调试器里看到的事实比任何贴吧答案都可靠。你甚至可以给HashMap的resize方法加一个断点看一眼newCap的计算过程比背十遍“扩容为两倍”都有用。4. 三个典型的第九章编程题解法推演我挑了三个在集合章节出镜率最高、也最经典的编程题演示完整解题流程。每一道都按“题目拆解、参考实现、验证方法”的顺序来讲。4.1 统计单词出现频率题目要求通常是这样给定一段文本统计每个单词出现的次数按出现次数从高到低输出。这类题的本质是分组聚合。拆解出来的步骤是分割文本、构建Map、排序输出。参考实现如下import java.util.*; import java.util.stream.*; public class WordCounter { public static void main(String[] args) { String text java is easy but java is powerful; MapString, Long freq Arrays.stream(text.split( )) .collect(Collectors.groupingBy(w - w, Collectors.counting())); freq.entrySet().stream() .sorted(Map.Entry.String, LongcomparingByValue().reversed()) .forEach(e - System.out.println(e.getKey() : e.getValue())); } }运行结果中“java : 2”排在最前面其余单词各出现1次。如果你不习惯流式写法用for循环也能实现同样的效果代码量略多但更容易读。关键是要理解Map在这里承担“键值聚合”的角色而不是机械背API。验证这道题的正误除了输出结果还要检查空字符串输入。如果text是空串split出来的数组长度为1里面是一个空字符串统计结果会是空串出现1次这往往不是题目想要的结果。所以完整解法应该在split之前先判空或者对空串做过滤。4.2 对列表去重并保持原始顺序题目要求通常是这样给定一个整数列表原地去重并保持元素第一次出现的顺序。这道题既考Set的去重能力又考List的顺序保持是典型的组合技巧题。最简洁的写法是用LinkedHashSet它内部既有哈希表的查重能力又有链表把插入顺序记录下来public class Deduplicate { public static void main(String[] args) { ListInteger list new ArrayList(Arrays.asList(3, 1, 2, 3, 4, 1)); SetInteger set new LinkedHashSet(list); list.clear(); list.addAll(set); System.out.println(list); // [3, 1, 2, 4] } }很多同学会用HashSet代替结果输出变成[1, 2, 3, 4]顺序被打乱这就是没分清HashSet和LinkedHashSet的区别。这种细节恰恰是习题要考核的点也是面试官最喜欢追问的点。验证方法很简单构造几个重复元素分布在首尾的用例比如[3, 1, 2, 3, 4, 1, 3]确保输出保留的是每个元素的第一次出现位置而不是最后一次。4.3 自定义对象排序题目要求通常是这样有一批学生对象包含姓名和成绩先按成绩降序排成绩相同按姓名升序排。这道题的考点是Comparable和Comparator的使用。可以让Student类实现Comparable也可以单独写一个Comparator。工程上更推荐Comparator因为它把排序规则和业务对象解耦以后排序规则变了不用去改业务类。class Student { String name; int score; Student(String name, int score) { this.name name; this.score score; } } public class SortDemo { public static void main(String[] args) { ListStudent students Arrays.asList( new Student(Alice, 88), new Student(Bob, 95), new Student(Cara, 88)); students.sort(Comparator.comparingInt((Student s) - s.score).reversed() .thenComparing(s - s.name)); students.forEach(s - System.out.println(s.name : s.score)); } }输出顺序是Bob、Alice、Cara。注意thenComparing的用法它是在第一排序条件并列时启用的第二条件。不理解comparing方法的话可以先用匿名内部类写Comparator功能一样只是代码长一点。验证这道题重点看稳定排序。成绩相同的Alice和Cara最终是否按姓名升序排列。把其中一条成绩改成和另一条相同再看输出顺序是否符合第二排序条件就能确认Comparator有没有写对。5. 常见问题与排查技巧实录这一节全是干货我把辅导同学过程中反复遇到的高频问题整理出来按“现象、原因、解决思路”的方式展开。5.1 代码在IDE里能跑为什么放到在线平台就报错这类问题的原因通常有三个。第一类名和文件名不一致多数在线平台要求主类名固定为Main你本地叫别的名字自然过不了。第二Scanner读取输入时没有处理换行残留整数读取完再读字符串经常读到空行。第三编码问题中文注释或字符串常量在Windows命令行下编译偶尔乱码。遇到在线平台报错先检查主类名再检查Scanner读取逻辑最后把源码编码统一成UTF-8。中文内容尽量不要硬编码在代码里可以提取到单独的资源文件这样能避开大部分编码坑。5.2 遍历时删除元素为什么老是漏删或者报异常这是集合题目里最经典的坑。看下面这段代码它看起来没有任何问题但实际输出会让人意外ListString list new ArrayList(Arrays.asList(a, b, c, b)); for (int i 0; i list.size(); i) { if (list.get(i).equals(b)) { list.remove(i); } } System.out.println(list);很多同学以为输出是[a, c]实际可能是[a, c, b]。原因是每次remove之后后面的元素会往前移动下标i指向的元素其实已经换了继续i就会跳过本该被检查的元素。解决办法是倒着遍历或者用Iterator的删除方法IteratorString it list.iterator(); while (it.hasNext()) { if (it.next().equals(b)) { it.remove(); } }如果用的是增强for循环循环内部直接list.remove()会触发ConcurrentModificationException。原因和处理方法在第2.3节已经讲过这里再提醒一次遍历中修改集合优先用迭代器。5.3 网传答案为什么经常对不上你的题目“Java第十版第九章”这个标题太模糊了至少好几本主流教材都有对应的第十版章节主题可能是集合、异常处理或者图形界面。就算教材相同不同印次的修订也可能导致章节顺序调整。你搜到的答案可能是旧版的也可能是另一本教材的。我的经验是先对照题目的截图或章节目录确认版本一致再局部核验不要整篇照搬。凡是遇到答案和你的运行结果不一致优先怀疑JDK版本差异再怀疑题目条件理解偏差最后才怀疑代码本身逻辑有问题。顺序反了你会浪费大量时间纠结在一个本来就没错的答案上。5.4 速查表现象最常见原因解决思路ConcurrentModificationException增强for中调用list.remove()改用Iterator.remove()或removeIf()HashSet去重后顺序变了正常现象HashSet不保证顺序需要保持顺序时改LinkedHashSetHashMap中文key乱码控制台编码问题源码和运行环境统一UTF-8运行超时数据量大还用了O(n^2)算法用HashSet或Map把查找复杂度降到常数级编译报重复符号类名和其他类重名用包名隔离或改类名TreeSet插入时报ClassCastException元素对象没实现Comparable让类实现Comparable或传入Comparator最后再说点实在的。我年轻时也借过同学的Java作业答案后来面试被问“HashMap如何解决哈希冲突”时答不上来场面极度尴尬。那次之后我养成了一个习惯任何题目都先自己写一版再去对照别人给的答案把不一样的地方逐行debug清楚。这不是鸡汤而是最省时间的学法——只有在你已经思考过的情况下参考答案对你才是有效输入否则它就是一堆你背不下来的代码。如果你正在为“Java第十版第九章习题全部答案”焦虑我的建议很朴素先从第2节的知识主线开始把List、Set、Map的底层关系理清再挑三道编程题自己写一遍最后用第3节的自证法验证。你亲手跑出来的输出比你搜到的任何答案都可靠。这一章学扎实了后面学集合操作、学Stream、学并发都会顺畅很多。