有赞2019校招Java笔试B卷复盘:核心考点与避坑指南 有赞2019校招Java笔试B卷这套题说实话我在准备校招那会儿刷过不止一遍它不像很多公司出的大而全试卷反而是把Java基础、集合原理、并发、JVM和一两个手写算法题卡在关键位置上答完能明显感觉到自己哪些地方是背过的、哪些地方是真懂的。放到今天回头看虽然年份有点久但里面的知识点依然活跃在各类Java面试题和Java面试八股文清单里你甚至可以把这套B卷当成一面镜子用来检验自己对Java基础的理解还剩下多少。这篇文章我就从笔试复盘的角度把B卷最常见的出题方向、每类题背后的原理以及我当时踩过的坑一起梳理出来适合正在准备校招、暑期实习或者想系统过一遍Java核心知识点的同学参考。1. 一场笔试的考古有赞 B 卷到底长什么样1.1 题型分布与出题风格有赞的校招笔试一般是牛客网或者自家系统上机完成B卷属于其中的一套题量大概在三四十道左右时间紧张纯靠临时翻书基本来不及。从题型结构来看大概能分成四块单选题、多选题、简答题、手写编程题部分批次还会追加一道开放设计题让你写思路而不是写完整代码。单选题和多选题重点集中在Java基础语法、面向对象编程、集合框架、异常处理、并发基础、JVM内存划分这些常规考点上偶尔会穿插一两个数据库SQL或者Linux命令题。简答题一般会让你写某个机制的原理比如简述HashMap在JDK 1.8中为什么引入红黑树谈谈volatile关键字的作用synchronized和ReentrantLock的区别。这些题目看着不难但真正落笔的时候很多人会发现脑子里的概念是散的写不出层次感。手写编程题一般有两道左右常见的是排序算法、字符串处理、链表操作这些类型。比如让手写快速排序Java实现或者给一个字符串要求反转、去重、统计词频。这道题当时难住我的倒不是算法本身而是代码的边界条件处理比如空数组、单元素数组、字符串为null时怎么办这些细节在笔试环境里特别容易漏。1.2 为什么 2019 年的卷子现在还能用很多人会问2019年的题到现在还有参考价值吗我的回答是校招Java笔试的核心范围变化很小甚至可以说Java面试八股文的题库这几年基本稳定下来了。你去看现在的Java面试题翻来覆去还是HashMap、JVM、并发、Spring、MySQL这些大模块只是深度变深了、追问变多了。但有一个点必须注意知识版本已经更新了。2019年主流的JDK还是8现在面试官可能会问你JDK 11、JDK 17的新特性比如本地变量类型推断var、switch表达式、文本块、record等。如果再翻出当年的笔记里面写的是Java 8的Lambda表达式这个基础还在但要额外补充新版本的变化否则笔试遇到源发行版 17 需要目标发行版 17这种编译提示你连原因都说不出来。这也是我把这套B卷定义为地基而不是全部的原因。2. Java 基础模块笔试里最拉分的地方2.1 面向对象三件套远没有表面那么简单面向对象编程Java是笔试选择题里的基本盘几乎每套卷子都会出现但很多人对这些概念的理解只停留在背诵定义。比如继承、封装、多态分别是什么都能说出个大概可一旦题目换成代码形式问你输出结果是什么就出问题了。举个例子父类有一个方法子类重写了它然后在子类构造函数里调用这个方法这个时候方法派发的是子类版本还是父类版本答案是在Java中构造函数调用虚方法会走子类的重写版本如果子类此时还没初始化完全就可能读到null字段。这就是一个典型的多态陷阱。再比如重载和重写的区别笔试喜欢考静态类型和实际类型的绑定关系。记住一句话方法重载看编译期类型方法重写看运行期类型。如果你把一个子类对象赋值给父类引用调用重载方法时选哪个重载版本取决于引用变量的声明类型而不是对象的实际类型。这种题我在B卷里碰到过当时差点选错。封装这块笔试很少考抽象概念更多是考访问修饰符的可见范围。public、protected、default、private这四个的可见性必须刻在脑子里尤其protected它不仅是同包可见跨包时还要求必须有继承关系才能访问。这个细节经常被拿来出多选题的干扰项。2.2 字符串与包装类笔试高频送命题字符串相关的题每次笔试我都觉得是送分题但成绩出来才发现其实是最容易丢分的地方。原因很简单String的不可变性、常量池、equals和之间的区别这些点已经被讲烂了但一旦换一个写法很多人还是会上当。比如两个字符串用new String(abc)和直接赋值abc它们之间用比较是什么结果这考察的是对象引用和常量池的差异。再比如字符串拼接代码里写String s a b c编译期会被优化成一个常量但如果中间加了一个变量比如String s2 a count b那么编译期就不能完全优化运行时会通过StringBuilder来拼接。很多人没有意识到这点写代码的时候就容易产生大量临时对象。还有StringBuilder和StringBuffer的区别一个是非线程安全但性能好一个是线程安全但加了同步开销。笔试选择题里经常问哪个类是可变的、线程安全的答案就是StringBuffer但实际项目里除非明确有并发拼接需求否则我更推荐StringBuilder。另外从JDK 8开始字符串拼接在编译期也会默认使用StringBuilder推进所以面试八股文里的用拼接会创建很多对象这个说法也要结合具体场景去看。包装类的考点集中在Integer缓存上。Integer默认缓存了-128到127之间的对象所以在这个范围内的Integer a 100; Integer b 100; a b结果是true一旦超过127就变成false。这个机制看似简单但笔试如果把范围改成Integer.valueOf(128)很多人就会踩坑。类似的还有Character缓存0到127、Boolean直接缓存两个实例。记住比较包装类数值一律用equals不要用。2.3 集合框架从 ArrayList 到 HashMap 的底层原理集合这块B卷至少会出三到四道题覆盖List、Map、Set的底层实现和适用场景。我当时复习的时候自己画了一张对比表把ArrayList和LinkedList的区别列出来再对照HashMap和ConcurrentHashMap的内部结构效果比干看文字好得多。ArrayList底层是Object数组默认容量10扩容时新容量是旧容量的1.5倍用的是oldCapacity (oldCapacity 1)这个计算逻辑面试常问。LinkedList底层是双向链表插入删除快、随机访问慢。笔试里经常出在ArrayList头部插入元素和LinkedList头部插入元素时间复杂度分别是多少这种题答案是ArrayList是O(n)LinkedList是O(1)因为链表只需要改指针。HashMap更是个重头戏。它底层是数组加链表JDK 1.8之后又加入了红黑树。链表长度超过8且数组容量大于等于64时链表会转成红黑树当红黑树节点数降到6时会退回链表。为什么阈值是8这是基于泊松分布算出来的在负载因子0.75、哈希函数随机性正常的前提下链表长度达到8的概率已经非常低选8是为了平衡时间和空间。扩容时HashMap的数组会翻倍原来的节点会重新计算位置JDK 1.8里利用高位运算把链表拆成低位链和高位链减少rehash带来的性能损耗。当时的B卷选择了一道JDK 1.7和1.8的HashMap有什么区别的简答题我写到了头插法改尾插法、引入红黑树、扩容时不再全部rehash这三点这三点现在依然是面试八股文里最常被追问的内容。2.4 异常体系与 try-with-resources异常相关的考点我在B卷里只遇到一两道选择题但在后续面试中反而被反复追问。异常体系的核心是Throwable下面分Exception和ErrorException又分受检异常checked和非受检异常unchecked/RuntimeException。受检异常强制要求捕获或抛出比如IOException、SQLException非受检异常不需要显式处理比如NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。笔试经常考的是finally和return的执行顺序。记住一个结论try或者catch中的return语句会把返回值暂存然后执行finally如果finally里也有return它会覆盖之前的返回值。所以不要在finally里写return这是代码规范里非常明确的一条禁忌。更高级一点的考法是try-with-resources这是JDK 7引入的语法可以自动关闭实现了AutoCloseable接口的资源比如文件流、数据库连接。基本原理就是编译器会把资源关闭逻辑转成finally块里的close调用且关闭时如果同时发生异常会自动把close阶段的异常添加为suppressed异常而不是覆盖主异常。这个点在普通八股文里容易被忽略但笔试如果考到异常链会很难受。3. 算法与思考题手写代码是笔试的硬门槛3.1 排序算法冒泡和快排为什么年年考有赞这套B卷的编程题里排序算法是我印象最深的一个方向。不是说题目直接让你写一个冒泡排序而是会包装成给定一个数组按从小到大排序要求时间复杂度尽可能低。但你一紧张脑子里首先跳出来的还是冒泡排序Java实现因为最好写、最容易验证。冒泡排序本身不难双层循环内层比较相邻元素把大的往后挪。但笔试评分会看你有没有做优化比如加一个flag如果某一轮没有任何交换说明数组已经有序可以直接退出。写出这个优化至少说明你理解冒泡的排序过程而不是死记代码。快速排序Java实现考察的维度更多。核心是分治加分区选定一个基准值pivot把小于等于基准的放左边大于基准的放右边然后递归处理左右两个区间。常见写法有Lomuto分区和Hoare分区笔试里写Lomuto更直观。需要注意几个点递归的终止条件是左边界大于等于右边界基准值可以选最左边、最右边或者随机选随机选能规避几乎有序数组导致的O(n^2)退化分区结束后基准值要放到正确的位置。我当时在自己电脑上反复做过对比实验同样是10万条随机整数手写快排Java实现比冒泡快了几十倍这个直观感受很重要因为面试官如果让你分析两者时间复杂度你能说出最好、最坏、平均情况以及为什么快排的平均复杂度是O(nlogn)而不只是背公式。3.2 字符串数组题的边界思维笔试编程题里另一类高频题型是字符串处理。比如给定一个字符串请反转其中的每个单词判断一个字符串是否是回文统计一段文本里出现次数最多的字符等。这些题目本身不难但难在边界条件。我印象很深的一次是在做字符串题目时没有处理字符串为null的情况直接调用length()方法结果代码在本地IDE里跑得好好的但笔试系统的用例里有一个null输入直接报了NullPointerException白白扣分。从那以后我养成了一个习惯任何字符串和数组题写代码前先想清楚三个边界——空引用、空集合、单元素集合。再比如反转字符串如果直接用JDK的StringBuilder.reverse()一行代码就完了但笔试想考察的可能不是这个。如果题目要求原地反转字符数组那就要用双指针一个指向头部一个指向尾部交换位置后向中间靠拢直到两个指针相遇。这种题考察的是对数组索引的敏感度也是很多人容易越界的地方数组越界异常非常容易出现在这种题目里。3.3 设计题与开放题怎么把思路写清楚部分批次的B卷最后会有一道开放设计题比如设计一个线程安全的单例设计一个固定容量的LRU缓存假设要做一个短链接服务你会怎么设计。这类题不一定要求完整代码但非常看重思路层次。以单例模式为例最简单的是饿汉式类加载时就创建实例线程安全但无法延迟加载。懒汉式可以延迟加载但直接不加锁会有并发问题于是有了双重检查锁。双重检查锁又牵扯出volatile关键字因为创建对象不是原子操作分配内存、初始化对象、把引用指向内存这三步可能被指令重排另一个线程可能拿到一个未初始化完成的对象。到了这一步就可以引出枚举单例了Effective Java里强烈推荐用枚举实现单例因为它天然线程安全、可以防止反射破坏和序列化破坏。我当时在笔试里并没有写出枚举单例只写到了双重检查锁加volatile但后来复盘发现如果能再往前写一步直接使用枚举类实现单例这题的加分效果会很明显因为这说明你看过经典书、理解得更深。开放题里逻辑清晰比代码堆砌重要得多你要让阅卷人一眼看到你的思路路径。4. 框架与工程化考点从笔试走向真实项目4.1 JVM 内存与 OutOfMemoryError 排查JVM这块是Java基础里比较硬的部分B卷里一般以选择题和简答题出现。考的最多的就是内存区域划分程序计数器、虚拟机栈、本地方法栈、堆、方法区JDK 8之后是元空间用本地内存实现。还会考哪些区域会发生OutOfMemoryError哪些会抛出StackOverflowError。我看到热词里有一个很典型的报错java: OutOfMemoryError: insufficient memory。这个出现在编译器或者IDE报错里通常不是堆内存不足而是创建子进程或者JVM启动时系统内存不足常见原因有机器物理内存不够、系统剩余内存碎片化严重、或者一次启动的JVM实例太多。当然笔试语境里更常问到的是堆上的OOM解决思路一般是调大-Xmx参数同时排查是否有内存泄漏比如集合里存了太多对象没释放、IO流没有关闭、ThreadLocal没有remove等。如果笔试里有简答题让你写排查思路可以按这个顺序回答先用jstat或者jmap -heap看堆使用情况再用jmap -dump导出堆转储文件然后用MAT或者JProfiler分析哪些对象占用了大量内存最后定位到代码里的问题点。这个流程不需要背屏幕输出关键是把定位到根因这条逻辑讲清楚。4.2 并发编程线程安全与锁并发相关的内容有赞B卷里占比不低尤其是synchronized和volatile的区别、线程创建方式、线程池参数这三个方向。先说线程创建方式严格来说Java里创建线程的方式只有一种就是new ThreadRunnable和Callable只是任务内容但笔试选择题会问以下哪些方式可以创建线程你要把继承Thread、实现Runnable、实现Callable结合FutureTask、以及线程池创建都选上别漏了线程池这个选项。volatile这个关键字很多人只记住可见性和禁止指令重排但它是基于内存屏障实现的。它无法保证原子性所以count这种操作在多线程下依然会丢数据。synchronized是互斥锁可以保证原子性、可见性和有序性但代价是性能开销更大。笔试喜欢出volatile能不能替代synchronized这种判断题答案是要分场景如果只是多线程读共享变量、单线程写可以如果是复合操作不行。线程池方面核心参数要记清楚核心线程数、最大线程数、空闲线程存活时间、任务队列、拒绝策略。笔试常考的是线程池的流程当任务提交时如果当前线程数小于核心线程数创建新线程执行如果大于等于核心线程数任务进入队列队列满了且线程数小于最大线程数创建新线程达到最大线程数执行拒绝策略。整个流程就像银行柜台柜台窗口是核心线程排队区是任务队列如果排队区也满了就临时加窗口再加不了就只能拒绝。4.3 Spring Boot 与接口安全笔试里越来越常见的场景题虽然2019年的笔试还是以JavaSE为主但跟Spring Boot相关的题目已经出现了主要形式是给一个场景让你设计接口。现在再看这类场景题越来越多比如热词里的java springboot apikey 安全对接就是一个很典型的实际项目需求。接口安全对接要考虑的点大概是这几层身份认证确认调用方是谁权限控制确认调用方能做什么数据完整性防止请求参数被篡改防重放防止同一个请求被反复利用。常见做法是给每个调用方分配一个appKey和appSecret调用方用appSecret对请求参数加签名后端用同样的算法校验签名同时时间戳参数限制请求的有效时间范围比如五分钟内有效。如果笔试让你设计这个流程你不需要写完整代码但要能画出这几步客户端生成签名、服务端校验签名、校验失败返回错误码。我当时在笔试里遇到类似的开放题就是先用文字把流程列出来再补一段简单的SignatureUtil伪代码把自己的思路讲清楚。注意安全相关的题不会要求你真正实现加密算法更多是在考察你是否有安全意识。4.4 环境与编译报错那些年我们踩过的 JDK 坑准备笔试的时候很多人只顾着刷题忽略了本地的Java环境配置。但真正到了笔试那天如果是自带电脑上机环境问题会直接让你心态崩掉。我这里总结几个高频报错。第一个是java: 警告: 源发行版 17 需要目标发行版 17。这个报错的意思是当前项目的源码编译级别是17但IDE里的目标编译级别不是17或者JDK版本低于17。解决办法是把项目的JDK版本、language level、以及maven的maven.compiler.source和maven.compiler.target统一成同一个版本。有时候改了Project Structure还不够还得去Settings里把Java Compiler的target bytecode version一起改掉。这类问题不涉及算法但对环境的理解很关键。第二个是Lombok相关的报错比如java: You arent using a compiler supported by lombok, so lombok will not work。一般是Lombok版本和JDK版本不兼容最常见的是JDK 17配了一个很老的Lombok版本解决办法是升级Lombok依赖到1.18.20以上或者直接减少对Lombok的依赖改用IDE自带的getter/setter生成功能。第三个是VSCode运行Java报错乱码。这通常是因为控制台编码和文件编码不一致Windows上默认GBK而Java源文件是UTF-8解决办法是在launch.json或者系统环境变量里加上JAVA_TOOL_OPTIONS-Dfile.encodingUTF-8或者在.vscode/settings.json里设置terminal.integrated.profile.windows相关编码。这些属于环境配置类问题虽然不在笔试考纲里但在机试现场它就是能决定你生死的问题。5. 笔试现场避坑指南这些细节决定你能否进面试5.1 时间分配与做题顺序有赞这套B卷整体难度对我这种非竞赛选手来说属于中等偏上时间压力主要在编程题因为你要一边回忆API一边考虑边界条件。我建议的做题顺序是先快速过一遍选择题遇到拿不准的题目先标记不要在一道题上卡超过三分钟然后做简答题因为简答题的主观性较强只要把核心点答出来就能拿到大部分分最后留出至少半小时给编程题。编程题如果两道都比较难我习惯先写自己有把握的那一道把卷面分拿稳。很多人一上来就死磕最难的题结果时间耗尽了后面会做的题也没时间写这是笔试最亏的地方。笔试不是竞赛目标不是满分而是在有限时间里拿到尽可能多的分数顺序策略非常关键。5.2 机试环境与代码规范细节校招机试一般是在网页编辑器里写代码没有本地IDE的自动补全和实时编译提示这意味着你对常用API的拼写必须足够熟悉。我因为平时太依赖IDE自动补全笔试时连HashMap的导包路径都要想一下这在紧张状态下非常影响心态。建议提前练习在纯文本环境下手写Java代码特别是一些常用类的方法签名。还有一点是代码规范。笔试系统一般不要求严格遵循团队规范但变量命名要做到见名知意比如count、index、pivot、result不要用a、b、c这种毫无信息量的名字。如果代码里有核心逻辑可以用注释简单写一句为什么这样处理比如此处选择随机基准值以避免最坏时间复杂度。这些细节在人工阅卷时是加分项。5.3 复盘方法把一套卷子吃透胜过刷十套最后说说复盘这件事。我刷完有赞这套B卷之后没有马上去做新题而是花了一个晚上把每道错题的知识点整理进一个表格比如ArrayList扩容因子Integer缓存范围try-with-resources异常抑制然后在表格旁边标注对应的参考源码位置。这个方法效率很高因为知识点的记忆需要锚点面试八股文里的目录结构其实就是很好的锚点框架。我会把这些内容按照Java基础、集合、并发、JVM、Spring Boot、网络、数据库七个模块归类之后每次遇到新问题就往对应模块里补充。等面试之前只需要翻自己的这一份笔记就能快速串联起整个知识网络。有赞的这套B卷只是起点它帮你暴露问题但真正让你成长的是问题暴露之后你做了什么。如果你能把一张卷子里考到的所有知识点都展开成一篇完整笔记那这套卷子的价值其实远大于刷十套同类卷子。