尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java测验5:综合自测40题,覆盖语法、集合、JVM与Spring Boot

Java测验5:综合自测40题,覆盖语法、集合、JVM与Spring Boot 这套java测验5是我最近整理出来的第五套Java自测题。和前面几套单纯刷语法、刷集合不一样这一套是从真正招聘季的面试题、以及开发群里大家踩得最多的环境坑里筛出来的混合卷一共40道题覆盖基础语法、面向对象、集合、Lambda、枚举、JVM内存、Spring Boot工程化这几大块。为什么值得做一遍因为它的出题思路更像真实面试和真实项目不是那种背了答案就能过的纯八股做完你大概能知道自己到底哪里是盲区。这套题最适合两类人一类是正在准备Java实习或初级岗位面试的用来快速定位自己知识点上的漏洞另一类是已经工作一两年、觉得自己平时写代码没问题、但一遇到面试题就发怵的。如果你处于这两个阶段花两小时做一遍比盲目刷十套题都管用。1. 这套测验的整体设计与选题逻辑1.1 为什么叫测验5而不是终极题库起这个名字的时候我其实没想过要搞多宏大。它就是我在整理学习笔记时输出的第五个版本前面四个版本分别聚焦单一专题第一套全是数据类型和运算符第二套全是面向对象设计原则第三套是集合和泛型第四套是并发和JVM。到了第五套我觉得有必要做一次混合体检把前面容易忽略的工程化问题也并进来所以这一套更像是一份综合卷。这个做法的价值在于实际面试从来不会按章节考你它会把多个知识点串在一个场景里。比如一道题问HashMap在并发下会怎样背后同时涉及集合、线程安全、JVM内存模型三个层面的知识。如果你只是按章节去学没有经过这种交叉自测很容易出现单章都会、组合就懵的情况。另外这套题在难度梯度上也刻意做了区分。前10题是给新手找回信心的基础题中间20题是面试高频的中等难度题最后10题则是带坑的易错题和工程场景题。做过的人反馈最集中的是前面的基础题偶尔也会翻车后面的工程题反而能蒙对不少这说明大部分人基础不牢、对工程化反而有直觉这个现象本身就值得反思。1.2 题型分布与答题建议这套测验的题型不是单一的。我的习惯是至少包含四类选择题、改错题、手写代码题、场景设计题。每一类都在考察不同的东西单纯刷选择题容易养成死记硬背的习惯所以我在第五套里增加了手写和改错的比例。建议答题方式是这样的准备一个空白的TXT文件或者白纸把答案先写下来别边看答案边做题那样一点意义都没有。选择题尽量控制在40分钟以内改错题20分钟手写代码题30分钟最后场景题10分钟整体控制在100分钟内完成。做完之后再对着解析逐题核对错的题不要只看正确答案要把相关知识点重新过一遍并且记到自己的错题本里。做完这套题后我强烈建议你给自己打个分。35分以上说明基础很扎实25到34分说明有漏洞但问题不大低于25分就需要退回基础阶段重走一遍。光这个打分动作就能帮你筛掉很多自我感觉良好的错觉。2. 高频考点拆解语法基础与面向对象2.1 运算符、表达式与标识符命名规则这套题的第一个高频板块是Java基础语法热搜词里java运算符和表达式java标识符命名规则持续有人搜说明这是初学者容易卡的入门关。我在测验里放了三道相关题一道考查运算符优先级一道考查表达式返回值类型一道考标识符命名合法性。运算符优先级这个考点很多人觉得背表格就行但实际面试很少考哪个最高哪个最低这种死题更多是混合表达式让你判断结果。例如int a 3; int b a a;这种题目如果a的初始值是3那么b等于多少拆解一下a先用后加此时取值为3a变成4a先加后用a变成5后取值5最终b是8。这种题看着简单但非常考验你是否真正理解先取值还是先自增这个底层逻辑很多人就是在这里丢分。关于标识符命名规则其实核心就几条只能以字母、下划线、美元符开头后续可以跟数字不能是Java关键字大小写敏感。面试里常出现的改错题是以下哪些命名合法比如int 2num;显然不合法int $var;合法int _var;也合法。很多人误以为美元符和中文不能用于标识符其实Java允许中文标识符只是强烈不建议用因为编码问题会让你被队友骂。2.2 面向对象三件套封装、继承、多态面向对象是Java面试的绝对核心热搜里面向对象编程java长期挂在榜单上。我在测验里专门设置了一道方法重载与重写区别的经典题。重载是编译期多态同一个类里方法名相同、参数列表不同重写是运行期多态子类重新实现父类方法方法签名必须一致。还有一个容易忽略的点重写方法的访问修饰符不能比父类的更严格抛出的异常范围不能比父类的更大。另外一个经常混在一起考的是String类。我在测验里放了这么一道题String str1 abc; String str2 new String(abc);问str1 str2结果是什么。答案是false因为str1指向常量池中的对象str2在堆里创建了新对象。如果改成str1.equals(str2)结果就是true。这道题的价值在于它同时考察了和equals的区别、常量池的概念、以及String不可变性的理解。我在辅导初级工程师时发现很多人能背出重载是编译期、重写是运行期这句话但让他现场写一个重写方法时会忘记加Override注解。虽然不加注解也能完成重写但加上之后如果方法签名写错了编译器会直接报错这在大型项目里能提前发现很多笔误。所以我的习惯是凡是重写父类方法一律加上Override这点也值得你在代码里坚持。3. 集合框架、Lambda与算法手写题3.1 HashMap与集合选择问题集合框架部分我重点放了HashMap和ArrayList/LinkedList的对比。HashMap几乎是Java面试的必考题从底层数组加链表、红黑树的结构到扩容机制再到JDK 1.8前后的变化每个点都可以往下深挖。我在这套测验里出的是一道相对隐蔽的题HashMap的默认初始容量是多少加载因子是多少什么时候触发扩容。答案是16、0.75、容量超过容量*0.75时触发扩容扩容后容量变成原来的两倍。很多人在实际开发中会忽略初始化容量这个参数。如果你能预估HashMap会存储大量数据我建议直接指定初始容量比如new HashMap(64)这样能减少扩容次数提升性能。这个优化在数据量达到几百万条时效果非常明显我在一个统计报表接口里实测过预分配容量后耗时下降了将近20%。不过要注意初始容量设置过大也会浪费内存一般设置为预估数据量的1.34倍左右比较合理。ArrayList和LinkedList的区别其实已经被问烂了但依然有人答不完整。记住这几个核心点就够了ArrayList底层是数组随机访问O(1)尾部插入O(1)中间插入和删除O(n)LinkedList底层是双向链表中间插入删除O(n)因为需要遍历找位置随机访问O(n)。很多人以为LinkedList插入一定是O(1)这是个常见误区除非你插入的位置是头部或尾部否则需要遍历寻址。3.2 手写快速排序算法题的正确打开方式算法手写题里我选了快速排序因为热搜词里快速排序java实现和冒泡排序java都是高频搜索。说实话面试官让你手写排序算法不一定是真的需要你去写一个生产级排序而是想看你思路是否清晰、边界条件是否考虑周全。所以这道题拿分的核心不是背出代码而是先说出思路再一步步写。快速排序的经典写法是这样的选一个基准值pivot把数组分成小于基准值、等于基准值、大于基准值三部分然后对左右两部分递归排序。我用Java写过一个比较简洁的版本用的是分治加双指针的思路public void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left (right - left) / 2]; int i left; int j right; while (i j) { while (arr[i] pivot) { i; } while (arr[j] pivot) { j--; } if (i j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; i; j--; } } quickSort(arr, left, j); quickSort(arr, i, right); }注意几个细节递归终止条件要写left right否则会无限递归取基准值用arr[left (right - left) / 2]而不是(left right) / 2因为后者在极端情况下可能溢出。手写时容易翻车的地方是left和right--的位置以及等于基准值时如何处理。这个版本里等于pivot的元素会被交换保证两边不会无限循环这点我已经在很多场景里验证过。另外我想多说一句能写出来是一回事写出来之后能说出平均时间复杂度O(n log n)、最坏O(n^2)、并且知道用随机基准值来避免最坏情况是另一回事。面试官通常会追问如果数组几乎有序会怎样这时候你要能答出需要随机化或三数取中处理。3.3 Lambda与Comparator函数式编程的实战点lambda函数 java和java comparator.comparing 将某元素值放第一个这两个热搜词背后反映的是实际开发里函数式编程和排序诉求的普遍性。我在测验里专门设计了一道题有一个ListUser需要按照年龄升序排序如果年龄相同则按名字字典序排序。最简洁的写法是用链式比较器users.sort(Comparator.comparing(User::getAge) .thenComparing(User::getName));但很多人在写某个元素放到第一个这种自定义排序时会卡住。比如你有一个状态列表想把RUNNING这个固定状态放到最前面其他状态保持原来的顺序。用Lambda可以这样写String target RUNNING; list.sort(Comparator.comparing((String s) - s.equals(target) ? 0 : 1));这个思路的核心是把排序规则转换成0/1值值为0的排前面。本质上这就是一个自定义Comparator只是借助Lambda优雅地表达出来。我工作中处理过很多类似需求比如按优先级、按角色等级、按下线状态排序都是这个套路。Lambda的几个使用细节也值得注意Lambda表达式的目标类型必须是函数式接口也就是只有一个抽象方法的接口比如Runnable、Comparator、Predicate。它不能完全替代匿名内部类的所有场景比如需要捕获多个变量、需要改变接口默认方法时直接用匿名类更清晰。另外Lambda在遍历Map时forEach((k, v) - ...)里的参数顺序也很容易搞错第一个是Key第二个是Value我在审代码时就看过好几次这种低级错误。3.4 枚举类型不只是常量类java枚举类型的使用也是一个搜索热度很高的词。很多人对枚举的理解停留在用枚举定义常量但面试如果深入问至少要从三个层面答枚举是一个完整的类可以拥有字段、构造器、方法枚举的构造器默认是private的枚举可以直接用在switch语句中并且每个枚举实例都有name()和ordinal()方法。我在这套测验里出了一道枚举相关的题考查的是枚举与策略模式的结合定义一个支付类型枚举每种类型带一个描述字段和一个处理方法引用。直接定义字段是最基本的关键是给每个枚举常量重写抽象方法这样可以实现一个简易状态机。比如public enum OrderStatus { CREATED(1, 已创建) { Override public boolean canCancel() { return true; } }, PAID(2, 已支付) { Override public boolean canCancel() { return false; } }; private final Integer code; private final String desc; OrderStatus(Integer code, String desc) { this.code code; this.desc desc; } public abstract boolean canCancel(); }这种写法的好处是状态相关的行为被内聚到了枚举本身其他地方调用时只需关注状态不需要写一堆if-else。我参与过的订单系统重构就把原来的状态流转判断从Service层抽到了枚举里代码量减少了一半以上可读性也强了很多。枚举在序列化和反序列化时也有特殊机制EnumMap和EnumSet这两个专用集合类也值得了解一下它们在性能上比普通Map和Set更优。4. JVM内存问题与异常处理4.1 OutOfMemoryError的真实场景热搜词里java: outofmemoryerror: insufficient memory是一个很具体的问题刚好我在测验里也放了一道JVM内存相关的题。很多人一看到OutOfMemoryError就慌实际上它不是一个单一错误而是一族。常见的有堆内存溢出java.lang.OutOfMemoryError: Java heap space、元空间溢出Metaspace、直接内存溢出Direct buffer memory、以及无法创建本地线程unable to create new native thread等。OutOfMemoryError: Java heap space是最常见的一般是因为堆内存设置太小或者代码中有对象无法被垃圾回收比如内存泄漏。我之前排查过一个定时任务接口每次执行都会往一个静态List里添加数据但从来不清理结果跑了几天后堆内存直接被打爆。我当时用jmap -dump:formatb,fileheap.bin pid导出了堆快照再用MAT分析一眼就看到那个List占了将近4GB之后定位到具体代码行只花了半小时。解决这类问题有几个固定步骤先使用jstat -gcutil pid 1000观察GC频率和内存增长趋势确认是不是持续上涨型然后用jmap导出堆转储文件再用Eclipse MAT或VisualVM分析大对象和可疑引用链。如果确认不是业务代码问题再考虑增加-Xmx参数比如在JVM启动参数里加上-Xmx2g甚至更高。不过我很反对一上来就调大内存因为很多时候内存溢出的根源不是内存不够而是代码里存在无法释放的引用调大参数只是掩盖了问题。4.2 数组越界异常与空指针的坑java中数组越界异常这个热搜词也很常见。数组越界异常ArrayIndexOutOfBoundsException和空指针异常NullPointerException是Java开发中出现频率最高的两个异常。说实话这两个异常本身很简单但它们在真实代码里经常组合出现。我见过一个初级工程师写的代码在循环里先list.get(i).getName()结果list里某个元素是null直接抛出空指针。排查时发现是上游数据源返回的某个字段为空代码里没有做非空判断。这引出一个经验在处理集合元素时尽量使用增强for循环或Stream流并且对可能为空的元素做预判。比如把list.stream().map(User::getName)改为list.stream().map(u - u null ? : u.getName())用Objects.requireNonNull在函数入口就校验关键参数。空指针的最佳解决策略不是事后抓异常而是从源头避免构造函数里初始化、方法入参加校验、HTTP接口返回前过滤空值。数组越界的另一个典型场景是二分查找、排序这些算法题里边界条件没处理好。比如我在这套测验里就放了一道二分查找的改错题不少人写的版本在left和right相遇时会漏掉最后一个元素或者进入死循环。解决这个问题的经验是统一用while (left right)或while (left right)并固定其中的一个写法每次写完都自己走几个边界用例空数组、一个元素、两个元素、target在最左、target在最右。4.3 编译期报错的排查思路热搜词里还有一个很有代表性的报错java: internal error in the mapping processor: java.lang.nullpointerexception这个一般是MapStruct、Lombok这类注解处理器在编译时挂了。处理这种编译期内部错误我总结了一套排查顺序先清理项目缓存Maven执行mvn cleanGradle执行./gradlew clean无效就升级或降级注解处理器版本再看是不是和JDK版本不兼容。这里特别要留意JDK 17及以上的模块化限制会导致很多老版本注解处理器无法正常工作此时需要换成与JDK匹配的新版本插件。还有一个非常常见的编译警告警告: 源发行版 17 需要目标发行版 17。这个问题的本质是编译器默认的--release或-source/-target版本不一致。解决办法通常是去项目构建工具里统一Java版本Maven项目改pom.xml里的maven.compiler.source和maven.compiler.targetGradle项目在build.gradle里设置sourceCompatibility和targetCompatibility。这些语法看着很基础但就是这种基础问题在群里反复出现我也专门在测验里放了这道题提醒大家不要在环境配置上翻车。5. 工程化与框架层面的测验题5.1 Lombok不生效的排查java: you arent using a compiler supported by lombok, so lombok will not work这个报错我相信很多人都见过。Lombok是在编译期通过注解处理器生成getter、setter、构造器等方法的工具库但它的实现原理决定了它对编译器和JDK版本非常敏感。比如你在JDK 16、JDK 17上用了老版本的Lombok就很容易出现不支持当前编译器的提示。解决办法从三个角度入手第一确认pom.xml或build.gradle里lombok的依赖版本是不是太旧我建议Lombok在1.18.30以上配合JDK 17使用比较稳定第二检查IDE里是否开启注解处理在IntelliJ IDEA中需要安装并启用Lombok插件并且在Settings - Build, Execution, Deployment - Compiler - Annotation Processors中勾选Enable annotation processing第三如果是在命令行用Maven编译注意编译器的注解处理器路径是否正确。这些坑都排查完之后绝大多数Lombok不生效问题都能解决。需要提醒的是Lombok虽然好用但团队开发中如果某个成员因为环境问题无法编译整个交付节奏都会受影响。我现在一般建议在公共基类或者查询结果对象上适度使用核心领域对象尽量显式写getter/setter既方便调试也避免过度依赖编译期魔法。这不是说Lombok不好而是要在便利性和可控性之间找平衡。5.2 Java环境变量配置与启动器选择环境变量配置是Java新手绕不开的坎热搜词里java环境变量配置和java环境配置也出现在榜单中。很多人在配置完JAVA_HOME后打开命令行执行java -version却发现还是旧版本这种问题十有八九是PATH变量里存在多个Java路径系统优先匹配到了前面那个。我在文章里反复强调过一个检查方法不要在系统属性里随便配而是用绝对路径把JAVA_HOME\bin加入PATH并且确认删除或调整其他Java相关路径的顺序。pcl(java版启动器)这个热搜词其实是Minecraft圈子里的启动器名称它之所以出现在Java热搜里是因为该启动器本身是一个Java应用很多玩家在自己电脑上安装后会出现Java运行环境缺失或无法启动的报错。这类问题通常和上面说的环境变量是一回事启动器找不到正确的Java路径。解决方法是直接把Java安装目录手动指定给启动器或者调整环境变量后重启启动器。虽然游戏启动器本身不是招聘面试的重点但如何定位Java版本、如何切换JAVA_HOME这个能力是通用的。还有一个偏门的词是drozer找不到java这个一般是指使用drozer这个工具时环境里没有正确安装JDK导致启动脚本找不到Java接口。这里的排查思路和上面一样先确认java -version能正常执行再看工具脚本里的JAVA_HOME指向是否正确。本质上环境配置类的报错大多可以用一句话归纳先确认Java本身能运行再往上层排查工具。5.3 Spring Boot API Key安全对接现在的Java面试已经不只是考语言本身框架知识占比越来越高所以我在测验里放了一道Spring Boot接口安全对接的题。场景是两个系统之间需要对接接口要求使用API Key进行身份验证防止未授权调用。最基础的做法是在application.yml里配置一个固定Key然后写一个HandlerInterceptor拦截指定路径校验Header里的X-API-Key是否匹配。public class ApiKeyInterceptor implements HandlerInterceptor { private final String validKey System.getenv(CALLER_API_KEY); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String apiKey request.getHeader(X-API-Key); if (validKey.equals(apiKey)) { return true; } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }这里有两个关键细节第一个是Key不能硬编码在代码里应该通过环境变量或配置中心管理否则代码泄露一次就全完蛋第二个是只做Header校验是不够的生产环境建议加上HTTPS、时间戳防重放、甚至签名验证。如果你希望更规范也可以使用Spring Security或Sa-Token这类框架。这个知识点平时写业务代码用得不多但在对接第三方系统和面试场景里很常见我把它放在测验里就是希望你意识到学了Java基础之后工程化能力同样重要。5.4 接口自动化测试框架思路java接口自动化测试框架也是一个高频词我在测验里设计了一道开放题让候选人描述自己会怎么搭建一个最小可用的接口自动化框架。这道题没有标准答案但它能在短时间内考察一个人的工程思维。我的思路一般是用JUnit或TestNG做测试组织用REST Assured或OkHttp发HTTP请求用JSON断言做结果比对最后接入Maven和Jenkins做持续集成。如果测试数据要做参数化我会选择TestNG的DataProvider或者JUnit 5的ParameterizedTest。如果项目里已经有Spring Boot基础直接用SpringBootTest配合MockMvc做接口层测试也是一种方案。这里我想特别强调一点接口自动化测试框架的核心价值不是能跑通而是能快速定位问题。所以日志一定要打全请求地址、请求参数、响应报文、断言失败的期望值和实际值都要完整输出否则用例失败后你还得去翻半天日志那就违背了自动化的初衷。6. 常见问题速查与避坑心得6.1 测试结果暴露的四个典型薄弱环节我在让几位初级工程师实际做这套题之后统计了几个反复出现的错误类型这里做一个速查表你可以对比自己的测验结果薄弱环节典型错误表现推荐补救方法运算符优先级混合自增表达式算错专项练习运算符/表达式题20道集合底层HashMap容量扩容答错画图理解底层结构手撕扩容流程Lambda与排序不知道thenComparing用法手写Comparator链式排序5个场景环境配置源发行版报错、Lombok失效按第5节排查步骤实际解决一遍这个表最大的用处是让你把不会量化成哪里不会。单纯做错一道题不可怕可怕的是错了之后不去归类下次换个马甲继续错。我会建议你把测验里的错题按上面四类归档然后有针对性地补效率会高很多。6.2 面试答题的节奏与表达技巧这套测验虽然是我用来练习的题目但它其实也是很好的面试模拟。选择题做得快不算本事真正拉开差距的是手写题和场景题。我见过不止一个候选人基本功很扎实但一上台手写快速排序就紧张写一半停下来导致面试官对他的评价大打折扣。关于手写题我有个屡试不爽的节奏先花10秒把算法思路用一两句话说清楚然后先在白板上画出关键指针或变量变化再开始写代码。面试官坐在对面最怕的是你一句话不说闷头写然后写到一半卡住而你先讲思路他不仅觉得你理解到位还会在你卡住的时候愿意提示你。对了写完代码之后一定要自己主动走一遍测试用例比如快速排序拿[5,3,8,4,2]跑一遍确认输出是[2,3,4,5,8]这个动作很加分说明你有测试意识。6.3 一个小建议每次测验后做一次回顾复习最后提一个我自己的习惯。我每次做完一套自测题都不会急着去刷下一套而是花30分钟写一个简单的回顾文档把错误的题目、错误原因、正确思路重新用自己的话写一遍。这个文档看起来很简单但它会强迫你从背答案变成讲道理记忆留存率会高很多。你回头去看自己的回顾文档也会发现自己是怎么一步步从混乱走向清晰的这个成长过程比再做十套题都有价值。7. 基于测验结果定制你的Java学习路线7.1 按分数段诊断并制定下一步做完这套测验并且对完答案之后你可以根据得分情况确定自己的学习路线。如果你得分很低说明问题不在题刷得少而在基础知识的体系还没建立起来。这时候我的建议是不要急着背面试题而是先按Java学习路线重新过一遍基础基本语法、面向对象、常用集合、异常、IO、多线程、JVM基础。只要把这几个模块彻底打通大概花两到三周再回来做这套测验分数会有明显提升。如果分数中等但主要集中在集合和Lambda相关的题目上出错说明你在实际开发中很少用到函数式编程和集合的高级API。这个阶段不需要重学基础可以每天拿真实业务场景练习比如把一个从数据库查出来的列表用流式操作完成过滤、排序、分组、求和。练习一段时间之后你会发现很多以前要写好几行的循环代码用Lambda和Stream几行就够了。如果分数较高只是偶尔在工程化题目上翻车那你的重点可以从语言本身转移到框架与工具链上Spring Boot、Maven/Gradle、Git协作流程、接口安全、持续集成。因为面试通常分两块一块是基础八股一块是项目经验基础过关后工程化经验往往会成为区分高分候选人的关键。7.2 实用学习资源与练习方法关于学习资源我没有太花哨的建议。基础语法和集合部分老老实实刷官方文档和某个系列的入门书就好不用贪多。重点是每学一个知识点都要配套练习不要只看书。学Comparator就去写一个实体的多条件排序学枚举就去写一个订单状态流转学泛型就去封装一个通用的分页返回类。这种即学即用的方式比光看不练高效得多。模拟面试也是一个很好的练习你可以找同学、同事或者录个视频在规定时间内把这套题讲一遍。很多人在做题时觉得自己懂了一开口讲才发现逻辑支离破碎。能够把一套题完整讲给另一个人听并且让对方听懂这才是真正的理解。我在带人时几乎不用再额外出题就让他把做过的测验题轮流复述一遍效果非常好。这套java测验5也完全可以这样用做一遍、讲一遍、再回顾一遍三遍下来这些考点的体系感就打好了。
返回列表