ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

2023届Java后端秋招模拟笔试复盘:高频考点与易错题解析

2026/8/31 4:31:30 拓冰建站 浏览量
2023届Java后端秋招模拟笔试复盘:高频考点与易错题解析 秋招那会儿我在牛客网上参加了2023届Java后端方向的模拟笔试一模。说实话模考卷子比很多厂的真实笔试题要规矩很多基础题占比高编程题不偏不怪但分数出来后我发现越是看着简单的题越容易在细节上丢分。这份模考最大的价值不是押题而是帮你把Java基础、JVM、并发、集合这些高频考点的薄弱环节一次性晒出来。这篇内容主要围绕一模试卷的题目类型、考察方向、易错点复盘和做题思路展开。无论你是正在准备秋招的应届生还是打算转行Java开发、想检验一下自己基础是否扎实的人都可以拿这套模考当参照系把大而全的八股文复习变成有针对性的查漏补缺。我尽量把当时做题时的真实感受和踩坑过程写清楚希望能帮你少走点弯路。1. 试卷整体印象这套一模到底在考什么先说一下这套卷子的整体观感。它不追求偏题怪题绝大多数题目都是面试八股文里的高频知识点但考察形式比纯背诵更有区分度。选择题会给你一段看起来没问题的代码然后让你判断输出结果编程题也不会直接让你“写一个快速排序”完事而是会带上一些边界条件和业务约束考察你代码的健壮性。试卷结构可以分为三块选择填空类客观题、代码阅读和输出题、在线编程题。客观题覆盖范围非常广从Java语言基础语法到集合源码、JVM内存模型、并发工具、异常处理、Spring Boot注解都有涉及。代码阅读题是这套卷子的精华它的每段代码背后都藏着一个经典八股文知识点比如字符串常量池、自动装箱缓存、HashMap的扩容条件、线程池拒绝策略等等。编程题方面我记得大概有两到三道一道偏算法实现另外的偏工程应用整体难度接近中等水平。这套卷子可能和你之前刷过的LeetCode风格不太一样。LeetCode通常只要求你完成算法过程而牛客的这套模拟题更贴近实际面试场景有的编程题需要你处理输入输出有的会要求你写一个类并提供多个方法有的还限制时间复杂度和空间占用。所以在准备模考时不能只练算法思路Java基础API的熟练度、代码规范度同样重要。1.1 题型分布与分值感受从我当时答题的体感来看选择和填空题占了大概50%以上的分值代码阅读加编程题占剩下的一半。客观题部分并不是简单问你“HashMap的默认容量是多少”而是换了种方式让你分辨“map.put到底发生了什么”。举一个印象很深的例子。有一道选择题问现有代码向HashMap中依次put了18个元素初始容量为默认值16负载因子0.75问最终数组的容量是多少。很多人潜意识里觉得扩容一次就够了实际上在插入第13个元素时就会触发扩容从16变成32后续继续插入则容量保持32。这套题考的不只是“扩容阈值容量*负载因子”这个公式还考察了resize过程中链表可能转红黑树的触发条件。如果不亲手写过源码或者看过源码分析这类题非常容易翻车。多选题的难度会再高一点。比如有一道关于线程池的题列出了四五个关于ThreadPoolExecutor构造参数的说法问你哪些是正确的。这种题表面考参数含义实际上考的是你对任务队列、拒绝策略和饱和策略之间关系的理解只记住异步任务的execute流程而没有深入理解各个参数的作用多半会漏选或多选。1.2 考察范围从基础语法到框架应用从热词来看今年Java笔试的重点依然是Java基础、集合框架、并发编程、JVM、Spring Boot和常见设计模式。这套一模试卷也没有偏离这个范围但考察方式明显更侧重“应用”而不是“背概念”。它不会直接问“什么是面向对象”而是会给你几个类让你判断它们的继承关系中哪些方法能被重写。Spring Boot相关的题目也有一定占比。比如有一个简答题或者选择题会问Autowired和Resource的区别以及在构造器注入、setter注入、字段注入三种方式中官方推荐哪一种以及为什么。这在牛客模考里经常出现实际面试也必问。另外不少同学反映今年试卷中出现了大量与Maven、打包运行相关的选择题这也和我做模考时的感受一致。坦白说这套一模试卷覆盖的知识面比大部分互联网公司的真实笔试要广一些像Java IO、NIO、反射、注解、泛型这些相对边缘的知识点也有涉及。但这些都不能算超纲而是属于一个合格的Java工程师应该掌握的基础范围。如果你只刷LeetCode不复习八股答题时会明显感到知识点不够用。2. 高频核心考点复盘与踩坑记录这一部分我会带着大家把试卷里出现频率最高的几个知识点逐个拆开结合作答时容易踩的坑详细说明。每个知识点我都会尽量还原题目的考察方式。2.1 面向对象与语言基础重载、重写、多态面向对象考察的点非常多一模试卷里出现最多的就是重载、重写、静态方法绑定和异常体系中父子类的捕获顺序。下面这段代码我印象很清晰它就是一道典型的代码阅读题class Animal { public void eat() { System.out.println(Animal eat); } public void eat(String food) { System.out.println(Animal eat food); } } class Dog extends Animal { Override public void eat() { System.out.println(Dog eat); } public void eat(int num) { System.out.println(Dog eat num); } } Animal a new Dog(); a.eat(); a.eat(bone); a.eat(1);这里有一个很容易被忽略的编译错误a.eat(1)看起来好像是调用Dog类新增的eat(int)方法但由于变量a的静态类型是Animal而Animal类中没有eat(int)方法编译直接失败。这个题目考查的正是编译期多态和运行期多态的区别方法重载是编译期根据参数列表决定的方法重写是运行期根据实际对象类型决定的。关于多态我还想提一个问题就是父子类静态方法的调用。Dog中如果定义了一个和父类完全相同的静态方法这其实叫隐藏不叫重写。试卷里有道选择题就特意设置了这个陷阱问输出结果是父类静态方法还是子类静态方法如果你按“动态绑定”的思路去判断就会直接掉坑。静态方法属于类调用时看引用类型不看实际对象类型。异常处理也是面向对象里绕不开的考点。有一道题给了这样的代码try { throw new RuntimeException(err); } catch (Exception e) { System.out.println(Exception); } catch (RuntimeException e) { System.out.println(RuntimeException); }这段代码实际上无法编译因为RuntimeException是Exception的子类先捕获Exception会导致后面的catch块永远无法到达。试卷用这种题目考察你对异常继承体系和catch顺序的理解而不是单纯问你运行时异常和受检异常的区别。2.2 集合源码HashMap、ArrayList、Comparator集合这块几乎是必考必答的重点。一模试卷关于HashMap的题目出得很有层次先是考默认容量和负载因子再考put流程和扩容时机最后甚至考到红黑树化条件。关于红黑树化很多人只记得“链表长度超过8转红黑树”但忽略了两个前置条件数组容量要大于等于64如果数组容量小于64即使链表长度达到8也只会触发扩容而不是转红黑树。试卷就有一道判断题专门考这个细节错的人很多。ArrayList的考点集中在扩容机制和Fail-Fast机制上。扩容方面默认容量是10每次扩容为原来的1.5倍也就是oldCapacity (oldCapacity 1)。这段代码隐藏了一个很多人没注意到的细节如果初始化时指定的容量为0第一次添加元素时并不会直接扩容到0而是会把容量设置为10。如果初始化容量不为0则第一次扩容就按1.5倍规则来。题目给你一个ArrayList初始容量为0连续add 11个元素问最终容量是多少很多人会算成15实际上是10。这个细节如果没看过源码基本答不对。Comparator的考察也很有意思有一道题给了一段排序代码ListInteger list Arrays.asList(3, 1, 2); list.sort(Comparator.comparingInt(Integer::intValue).reversed());这道题本身不算难难的是它后面加了一个要求把某个固定值固定排在最前面其余元素按自然序。比如有一个对象列表希望状态为“启用”的对象排在最前其他对象按创建时间倒序。正确写法一般是这样list.sort( Comparator.comparing(User::getStatus, (s1, s2) - { if (启用.equals(s1)) return -1; if (启用.equals(s2)) return 1; return 0; }) .thenComparing(User::getCreateTime, Comparator.reverseOrder()) );这种题在真实笔试中经常出现因为它很贴近业务场景。需要注意的是Comparator的reversed方法会反转整个比较器的结果如果你的比较器内部已经写了复杂的业务逻辑反转后效果可能完全不符合预期。更安全的方式是直接用Comparator.comparing(...).reversed()只反转当前键的比较结果。2.3 Lambda与函数式编程Lambda表达式的考点主要是函数式接口、变量捕获和Stream API基本用法。一模试卷里有一道题说下面这个方法的输出是什么int x 10; Runnable r () - System.out.println(x); x 20; r.run();按常理很多人会以为Lambda捕获的是变量值输出10。但这段代码其实根本无法编译因为Java的Lambda只能捕获“有效final”的局部变量也就是初始化后不再变化的变量。如果你在Lambda定义后又修改了x编译器会直接报错。这是Java 8以来特别经典的坑几乎年年考。还有一道Stream的题是给了一个字符串列表要求去掉空字符串、转大写、按长度排序并收集为List。这种题目如果没有实际写过Stream很容易在API选择上出错。比如Collectors.toList()和Collectors.toSet()的使用场景不同sorted()和sorted(Comparator.reverseOrder())的排序结果也完全不同。建议复习时把map、flatMap、filter、reduce、collect、groupingBy这几个方法至少手写一遍笔试时能节省大量时间。2.4 并发编程线程安全、锁和线程池并发是这套模考比较拉分的地方。有一道关于synchronized的代码题考的是类锁和对象锁的区别public class SyncDemo { public synchronized void methodA() { ... } public static synchronized void methodB() { ... } }两个线程分别调用同一实例的methodA和methodB问它们是否互斥。答案是不互斥因为methodA锁的是当前实例对象methodB锁的是当前类的Class对象两者是不同的锁。如果换成两个不同实例分别调用methodA那也不互斥但两个线程调用不同实例的methodB时由于锁的是同一个Class对象它们会互斥。线程池的题目很多高频考点包括核心线程数、最大线程数、任务队列大小、拒绝策略之间的关系。有一道题给了这样的场景核心线程数是2最大线程数是4阻塞队列容量是2向线程池提交了6个任务问有多少任务可以同时执行。很多人会直接答4个实际上线程池的执行顺序是先创建核心线程执行前2个任务后续任务优先放入阻塞队列队列满后再创建非核心线程执行任务当线程数达到最大值并且队列也满时才触发拒绝策略。所以第3、4个任务会进入队列第5、6个任务会创建非核心线程执行最终同时执行的任务数是4个但队列中还有2个任务在排队。这种题目一定要搞清楚“先放队列再开新线程”的执行逻辑而不是想当然地以为任务都会立即被执行。并发工具类方面CountDownLatch、CyclicBarrier、Semaphore的区别是选择题常客。最容易混淆的是CountDownLatch和CyclicBarrier前者是让一个或多个线程等待其他线程完成操作计数不可复用后者是让一组线程互相等待到达某个屏障点后继续执行计数可以重置。如果题目说“多个线程各自执行任务全部到达后再统一开始下一阶段”优先选CyclicBarrier。2.5 JVM与内存异常OutOfMemoryError与排查思路热词里出现了java: outofmemoryerror: insufficient memory这个错误在实际开发中很常见一模试卷里也出了相关的题。考察点是JVM运行时数据区以及不同OOM类型对应的区域。常见的内存异常可以分成几类堆内存溢出对应java.lang.OutOfMemoryError: Java heap space栈溢出对应java.lang.StackOverflowError通常因为无限递归方法区或元空间溢出对应java.lang.OutOfMemoryError: Metaspace常见于动态生成大量类还有直接内存溢出会报OutOfMemoryError: Direct buffer memory。有一道题给出了一段不断往List里添加大对象的代码问会抛出什么异常以及如何解决。这题的正确答案是堆内存溢出解决思路包括确认对象是否真的需要常驻内存、检查是否有集合引用未释放、增大堆内存参数、使用弱引用或软引用处理缓存。知识点本身不复杂但很多人会把栈内存和堆内存搞混导致题目判断错误。排查OOM的实操方法我认为是笔试和面试共同重视的能力。如果线上遇到OOM一般步骤是先通过启动参数加上-XX:HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath/data/logs保存堆转储文件再用MAT或JVisualVM分析大对象同时结合GC日志看是内存泄漏还是内存分配速率过高。模考里虽然没有让你写完整排查过程但会问你“下列哪些参数可以在OOM时导出堆快照”这个知识点要记住。另外热词里还有java: 警告: 源发行版 17 需要目标发行版 17这是Maven或IDEA编译版本不统一引起的。出现这个警告通常是因为项目的JDK版本和编译器源码级别不一致。解决办法是检查IDEA的Project Structure里Project SDK、Modules中的Language Level以及Maven的Compiler Plugin配置把maven.compiler.source、maven.compiler.target统一成同一个版本。模拟笔试的编程题环境一般不会让你改配置但在自己的IDE里跑代码时这个坑非常常见我单独提一下。2.6 异常处理与工程类代码细节异常处理在客观题里出现频率很高。热词里有一条java基础面试题里常常包含的内容是“受检异常和非受检异常的区别”。一模的题目会在代码里故意让某个方法声明抛出InterruptedException或IOException然后在调用处不处理问编译能否通过。这类题目没有捷径只能把常见的受检异常记牢IOException、SQLException、InterruptedException、ClassNotFoundException、FileNotFoundException等。RuntimeException及其子类不属于受检异常比如NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException。另外试卷里出现了java: internal error in the mapping processor: java.lang.nullpointerexception这一类报错信息的题目。这看起来是个环境或者编译错误但考察点其实包括两个一是你是否理解Lombok或MapStruct这类注解处理器在编译阶段的作用二是你能否根据报错信息定位问题。比如Lombok在JDK版本不兼容时会报java: you arent using a compiler supported by lombok, so lombok will not work这个问题通常是因为IDE内置的编译器版本过旧或Lombok版本太低。实际开发中遇到这类问题优先检查Lombok版本和IDE的Java编译器版本。工程类考点还有一个常见的坑就是数组越界。热词java中数组越界异常对应的就是ArrayIndexOutOfBoundsException。正常笔试不会直接让你判断一个越界代码而是把它包装在二分查找、循环遍历这类场景里让你找代码的Bug。比如下面这段代码问题非常隐蔽int[] arr {1, 2, 3, 4, 5}; for (int i 0; i arr.length; i) { System.out.println(arr[i]); }很多同学一眼就能看出越界因为i arr.length导致最后一次循环访问arr[5]。但如果把数组换成一个字符串数组并且通过split方法得到有些人就会忽略空字符串或尾部分隔符的情况产生不必要的越界。笔试题就是通过这种方式提醒你写代码时一定要关注边界条件。3. 算法与编程题不只是排序编程题部分一模试卷考了两道让我印象非常深刻的题。一道是排序相关另一道是字符串处理。虽然题目本身不算难但在线编程环境下很容易因为边界条件处理不到位而丢分。3.1 快速排序与冒泡排序的边界细节热词里出现了快速排序java实现和冒泡排序java说明这两个排序算法确实是Java笔试的高频考点。一模的编程题就有一道要求实现一个排序方法然后对给定数组排序输出结果。看起来是送分题但实际上有很多细节可以扣分。先看快速排序。常规实现是选取基准值然后分区递归排序。我当时的实现如下public void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left]; int i left; int j right; while (i j) { while (i j arr[j] pivot) { j--; } arr[i] arr[j]; while (i j arr[i] pivot) { i; } arr[j] arr[i]; } arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }如果笔试要求你用这个写法有两点必须注意。第一内层while循环的等于判断不能漏掉 pivot和 pivot可以避免无限循环第二递归终止条件必须是left right而不是left right否则当区间长度变为0时还会继续递归。我看有些同学的代码会在原地交换时用经典的swap法结果因为基准值选取不当在最坏情况下时间复杂度退化为O(n^2)笔试中的全量用例超时失分。冒泡排序相对简单但有道选择题考了一个变种记录最后一次交换位置用它作为下一轮循环的边界可以减少无效比较。这种优化叫“鸡尾酒优化”或“冒泡排序改进”代码大体如下public void bubbleSort(int[] arr) { int n arr.length; int lastSwap n - 1; while (lastSwap 0) { int swapPos -1; for (int i 0; i lastSwap; i) { if (arr[i] arr[i 1]) { int temp arr[i]; arr[i] arr[i 1]; arr[i 1] temp; swapPos i; } } lastSwap swapPos; } }如果题目要求“比较次数尽可能少”这个优化往往能成为得分点。平时刷题时不能只满足于“能排序”还得清楚不同写法的复杂度差异。3.2 字符串处理正则、去重、统计字符串处理这道编程题题目大意是统计给定字符串中每个字符出现的次数并按要求输出。看起来简单但有几个约束条件容易漏掉字符是否区分大小写、是否包含空格、输出顺序是按字符ASCII码还是按出现次数。我当时的实现里用了一个LinkedHashMap来保持字符插入顺序然后用Map.Entry遍历输出。结果题目要求的输出顺序是“按字符的字典序”也就是HashMap和LinkedHashMap都会出问题。实际上正确答案应该先把键排序或者直接使用TreeMapMapCharacter, Integer countMap new TreeMap(); for (char c : str.toCharArray()) { countMap.put(c, countMap.getOrDefault(c, 0) 1); } for (Map.EntryCharacter, Integer entry : countMap.entrySet()) { System.out.println(entry.getKey() : entry.getValue()); }这个题提醒我一个道理刷题时不要只关注算法逻辑输出格式、排序规则、是否去重同样决定你能否通过所有用例。很多人在牛客上做在线编程题方法写对了但提交后只过了一半测试用例就是这个原因。4. 做题策略与时间分配模拟笔试和真实校招笔试一样时间有限题量不小不管基础好不好答题策略都会直接影响分数。一模的题量我记得不算少如果在一道选择题上纠结太久后面很可能来不及做编程题。4.1 时间分配与答题顺序我的建议是拿到试卷后先把编程题浏览一遍用一两分钟判断难度。如果编程题中有明显的送分题优先做如果全是中等以上难度那就先集中精力做客观题把能拿的分先拿到手。网上有很多人说“一定先做编程题”但这个策略要分情况。牛客模考的客观题占比很高且很多选择题考察的是纯记忆性知识点会就是会不会就是不会不存在“卡壳—跳题”的可能性所以客观题性价比很高。我通常的做法是前15分钟完成选择题和判断题遇到拿不准的标记一下不恋战。然后留出35到40分钟做编程题。最后再回头检查之前的标记题。这套流程可以避免因为某道偏题耽误整体节奏。时间允许的话代码写完后一定要自己构造几个边界用例测一下包括空数组、数组长度为1、字符串为空、字符串全为相同字符等情况。有一次我在模考里写字符串统计题只测了普通字符串就提交结果漏掉了字符串包含空格时TreeMap排序的细节白白丢了一部分测试用例的分。总之边界用例是编程题提分最快的方式。4.2 如何利用模拟笔试的复盘报告牛客模考结束后会提供答卷分析和排名信息这个复盘报告非常有用。不要只看分数要逐题分析错因。我把自己的错因分成了几类基础概念混淆、代码输出结果判断错误、边界条件遗漏、时间不够。这样分类之后复习方向就很明确了。比如如果发现自己常错在“集合类的输出顺序”上就专门把ArrayList、LinkedList、HashMap、LinkedHashMap、TreeMap、HashSet、LinkedHashSet、TreeSet的遍历顺序全部整理一遍。如果常错在“线程池执行流程”上就手画一个线程池处理任务的流程图把提交任务、核心线程、任务队列、非核心线程、拒绝策略这个链条彻底理清楚。比盲目刷十套模拟题有效得多。5. 牛客模考里常见问题与排查技巧实录最后这部分我说说在模考过程中容易遇到的非知识点类问题这些问题往往和笔试环境、本地环境有关但也会直接影响代码能否通过。5.1 在线编程环境中的输入输出问题牛客的在线编程题输入输出格式非常关键。很多代码在IDEA里能跑通但提交到线上就报错或者只过部分用例原因往往是输出格式不符合预期。比如要求输出结果每个数字之间用空格分隔最后不能有多余空格要求输出一行结果后换行。这些细节在本地看并不重要但在自动评测系统里一个字符都不能差。建议平时练习就按牛客的输入输出规范来避免到笔试时手忙脚乱。有一个处理多行输入的小技巧如果题目没有明确说输入有多少行不要用固定长度的Scanner读取而是用while (scanner.hasNextLine())做循环读取这样能适配不同行数的测试数据。字符串输入用next和nextLine时也要特别注意next不会读取空格部分nextLine会读取整行如果混用在换行符处理上会出现问题字符串题目里尤其常见。5.2 IDEA和编译环境常见问题Maven配置、Lombok、源发行版警告如果你打算用本地IDEA把模考编程题再敲一遍大概率会遇到一些环境问题这里我直接把排查方法列出来。Maven项目编译时如果提示java: 警告: 源发行版 17 需要目标发行版 17一般可以从三个地方排查。第一是IDEA的Project Structure确认Project SDK和Project Language Level都选择同一版本第二是Maven配置检查pom.xml里有没有设置maven.compiler.source和maven.compiler.target如果没有建议显式加上第三是IDE的Settings里Java Compiler的Target bytecode version必须和项目语言级别一致。如果这三个地方版本不一致就会出现编译警告或者更低版本的错误。Lombok相关报错在笔试复习阶段出现的频率也很高。典型提示是java: you arent using a compiler supported by lombok, so lombok will not work。原因通常有两个一是Lombok版本太旧不支持当前的JDK版本比如JDK 16之后对Annotation Processor的加载方式有变化旧Lombok就会失效二是IDEA内置编译器版本与Lombok不兼容。解决办法是升级到最新Lombok版本同时在IDEA里安装Lombok插件并开启Annotation Processing。如果还是不行把Maven的编译插件版本也升一下比如maven-compiler-plugin升到3.10.1以上很多兼容性问题都会消失。另外热词里有一项是vscode运行java报错乱码这类问题本质是编码不一致。Windows下默认编码可能是GBK而Java源文件保存成UTF-8运行输出中文时就会乱码。排查顺序是先确认文件编码是不是UTF-8再确认编译参数里是否加了-encoding UTF-8最后确认终端代码页。如果用的是Maven可以在pom.xml里显式配置项目编码为UTF-8properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties这个配置在很多团队里已经成了标配但面试中如果提到你处理过编码问题会是比较好的加分项。5.3 常见易错点速查表我把一模做错率比较高、也最容易在真实笔试中出题的知识点整理成一个速查表方便你在考前快速过一遍。知识点易错细节正确结论HashMap扩容容量达到阈值才扩容默认容量16阈值容量*0.75链表长度达8且数组容量小于64时优先扩容ArrayList扩容初始容量为0时首次扩容首次add时容量从0直接设为10不是1.5倍线程池任务执行顺序先放队列还是先建新线程核心线程占满后先入队队列满后才创建非核心线程synchronized修饰类锁和对象锁不互斥静态方法锁Class对象实例方法锁当前对象Lambda变量捕获变量能被修改吗被捕获的局部变量必须是有效final不能被修改受检异常编译是否通过IOException等必须处理RuntimeException不检查Comparator排序reversed的作用范围反转的是整个Comparator比较结果注意链式调用顺序快速排序相等元素内层循环等于判断不可省略和否则可能无限循环这张表里的内容可能和某些具体公司笔试题不完全一样但涉及到的底层原理基本是通用的。我每次做模考前都会把这些点快速过一遍目的是让大脑在遇到变体题时能第一时间联想到考察方向。最后一点体会一套模考做下来我最大的感受是Java面试考的不是你会不会背某一条结论而是你在实际写代码时能不能想到这个结论。同样是HashMap背得出默认容量是16和能判断出连续插入13个元素后数组容量变成32是完全不同的两种水平。牛客这套一模卷子恰恰能把这两种水平的差距直观地量化出来。如果你正准备类似的秋招笔试我建议不要只盯着分数和排名多花时间分析错题背后的原理。把每道错题当成一个小项目去补先还原当时错误的想法再看正确结论对应的源码或标准实现最后自己去写一遍相似的代码。这个过程虽然比单纯刷题慢但效果要牢固得多。下次再碰到同样的知识点即便题型变了你也能很快识别出它的真实考察意图。