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

资讯详情

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

Java二星级练习卷:高频基础考点与工程避坑实战

Java二星级练习卷:高频基础考点与工程避坑实战 做了这么多年Java开发也带过不少刚入行的新人我越来越觉得一个朴素但很有用的道理大部分Java工程师的基础功底并不是靠刷了几个框架就扎实的。尤其到了面试或者接手老项目的时候暴露出来的问题往往集中在语言本身的细节上——运算符优先级、集合的底层逻辑、异常处理边界、环境变量的配置。这些内容不复杂但“会用”和“讲得清”是两码事。所以前阵子我整理了一套内部练习卷题目难度定在“二星级”定位非常明确不是刁钻的算法题也不是深不见底的源码分析而是覆盖Java工程师日常开发中最高频、最容易踩坑的那批知识点。这篇文章就把这套卷子的出题思路、核心题目拆解、实操过程中的典型报错以及我自己的批改心得完整写出来。已经工作一两年的朋友可以用它自查一下有没有盲区准备面试的初级工程师也能拿它当一份浓缩版的复习清单。1. 这套二星级练习卷到底在考什么1.1 二星级的定义不是简单而是高频现在网上一搜“Java面试题”就是一大把“八股文”动辄JVM调优、并发编程、Spring源码看起来很高大上但说实话大部分初级岗位用到的东西根本没到那个深度。我给这套卷子定“二星级”参考的不是难度模型而是出题频率模型凡是过去一年里让我手底下新人在实际开发中卡壳超过一次的Java基础问题我都收了进去。这套卷子一共分成四个部分语言基础与面向对象、集合与常用API、异常与JVM基础、编码题与工程配置。每一部分的题目都不是孤立的后面跟的小题往往涉及同一个知识点在真实场景里的变形。比如考“数组越界异常”的时候我顺便把ArrayList的subList陷阱也放进来了因为这两者在实际业务代码里经常连环踩坑。从批改结果来看90%的参与者能把选择题做对80%以上但一到“阅读代码说输出”和“现场配置环境变量并编译运行”这两个环节正确率就掉得非常明显。这说明一个老生常谈的问题——很多人学Java是看会的不是写会的。1.2 知识点地图这些题覆盖了哪些核心模块如果你正准备用这套卷子自测先看一眼覆盖范围语法基础基本数据类型与包装类、和equals的区别、字符串不可变性、运算符优先级、标识符命名规则。面向对象封装继承多态的实际应用、构造器调用顺序、static与实例成员的关系、对象组合与继承的取舍。集合框架List/Set/Map的底层结构特点、HashMap的扩容机制、ArrayList与LinkedList的适用场景、排序与比较器。函数式编程Lambda表达式的使用、Comparator链式写法、stream的常用中间操作与终止操作。异常与JVM基础受检异常与非受检异常的处理原则、OutOfMemoryError的常见类型、数组越界与空指针的防范。工程与环境JDK安装与环境变量配置、javac与java命令的基本使用、常见编译错误的解读、Lombok等工具的基本使用。看到这个清单你可能会觉得“都是基础”但二星级恰恰就是把这些基础中最容易看走眼的地方挖出来。比如HashMap扩容相关的题目很多人的第一反应是“默认容量16”但题目稍微换成“负载因子0.75放入13个元素会扩容几次”就有一批人交白卷。1.3 为什么这套卷子不追求偏题怪题我见过不少练习卷喜欢出“volatile和synchronized的底层实现区别”这种题说实话对一个刚入行的人来说这种题背下来没有意义因为工作里根本遇不到需要这种知识的场景遇到了也不是靠背能解决的。二星级的定位应该是覆盖所有业务的公共底座——任何Java项目哪怕是一个main方法跑起来的工具类都会用到前面列的那些知识点。所以我在设计题目时坚持一个原则每一道题都要能追到一个真实的报错或一个真实的业务场景。考和equals就配一个Integer缓存范围的代码段考集合就配一个HashMap在并发下丢失数据的案例考Lambda就配一个把某元素排到第一位的Comparator.comparing写法。这样做的好处是做题的人不是为了做题而做题而是能通过题目反推自己平时写代码时有没有埋雷。2. 高频理论点拆解这些题背后的原理与坑2.1 Java基础题里最容易被绕进去的五个点先来讲解一下这套卷子中出错率最高的几个基础题每个都是真实工作场景的映射。第一个Integer的缓存问题。题目给了一段代码Integer a 127; Integer b 127; Integer c 128; Integer d 128; System.out.println(a b); System.out.println(c d);答案是true和false。很多人只记住“-128到127之间有缓存”但不理解为什么128就不一样。实际上Integer默认缓存了-128到127之间的对象valueOf在缓存范围内会返回同一个实例而超出范围就会new一个新的对象。所以用比较两个包装类对象时本质上比的是引用而不是值——这个知识点在业务代码里最容易出的问题就是“用比较金额线上数据恰好超过127就出bug”。正确的做法是永远用equals或者用Objects.equals。第二个String的不可变性与常量池。题目考了类似这样一段代码String s1 java; String s2 java; String s3 new String(java); System.out.println(s1 s2); // true System.out.println(s1 s3); // false System.out.println(s1.equals(s3)); // trues1和s2都指向常量池里的同一个java而new String(java)会在堆里创建新对象。这个知识点看似简单但后面跟了一个追问“s1.concat(面试)之后s1本身会变吗”答案是不变因为String是final的所有修改操作都会生成新对象。工作里最常见的就是在大循环里用拼接字符串导致内存占用飙升所以二星级卷子里专门放了一道“循环一万次拼接字符串应该用什么”的题。第三个数组越界异常ArrayIndexOutOfBoundsException本身不算难但卷子里加了一层业务包装一个商品列表按页返回页码从1开始代码里直接用pageNum * pageSize作为循环起点结果第一页就崩了。很多人调了半天才反应过来应该是(pageNum - 1) * pageSize。这种题目考的不只是异常本身而是边界思维——排查数组越界的时候别光看数组定义还要看循环的起点和终点、subList的左闭右开、substring的边界。第四个运算符优先级。卷子出一道“int x 5; int y x x;求x和y的值”的题答案能说清楚的人不到一半。这里不是考算术能力而是考查一个习惯问题代码里写这么复杂的自增表达式本身就是坏味道。我在批改说明里专门写了一句这个题的真正正确答案是“以后别这么写拆成两行或三行”。优先级的本质是让代码更可读而不是让读者去查表。第五个标识符命名规则。这题表面很简单但很多人忽略了一个细节Java标识符可以用中文、美元符号、下划线开头但不能用数字开头也不能是关键字。实际工作中唯一要记住的规范其实是团队约定的驼峰命名笔试时你只需要记住“class、public、static这些不能当变量名$和_虽然合法但不要在正式代码里用”就足够了。2.2 面向对象与常用类的精讲面向对象部分卷子里最值得聊的一道题是构造器调用顺序。给出一段代码父类有静态代码块、实例代码块和构造器子类同样也有然后问创建子类对象时输出顺序是什么。答案是父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。这道题背后隐藏着两个特别实用的开发经验。第一不要在父类构造器里调用可被重写的方法因为子类对象还没初始化完成调用子类重写的方法极有可能拿到null。第二静态代码块只执行一次在类加载时触发而实例代码块在每次创建对象前执行。理解了这两点再看Spring的Bean生命周期就会顺很多因为Bean的初始化顺序本质上就是“先构造、再填充属性、再执行初始化方法”。常用类这块我挑了两个练习卷的必考方向。一个是枚举类型的使用卷子会让写一个“订单状态枚举”包含待支付、已支付、已取消三个状态每个状态要能返回描述和操作按钮文案。这个题考察的不只是enum语法而是枚举携带数据和行为的能力——很多初级工程师习惯用一堆常量字符串去表示状态到后面状态一多就变成维护噩梦而枚举天然适合做这种有限集合的数据建模。另一个是java.util.Objects工具类卷子要求用Objects.equals重写某个实体类的equals方法同时重写hashCode。这里想强调的就是一个面试必问题为什么重写equals必须重写hashCode因为HashMap、HashSet这类集合会先用hashCode定位桶再用equals确认对象是否相同如果你只重写equals而不重写hashCode就会出现“把对象放进Set后用内容相同的对象查不到”的诡异问题。2.3 集合框架与Lambda的实战逻辑集合框架部分我出题时最看重的一个对比是ArrayList和LinkedList。很多人背过“ArrayList底层是数组LinkedList底层是链表所以LinkedList插入删除更快”这个话在理论上勉强成立但在实际编码中基本是错的。因为LinkedList每次插入都要维护前后指针而且内存不连续在绝大多数真实场景里效率反而不如ArrayList。卷子里给定的题目是一个日志系统需要按时间顺序不断在末尾追加记录偶尔按索引查询问选择哪种结构。正确答案是ArrayList因为尾部追加和按索引随机访问都是ArrayList的强项。所以二星级的集合题考的不是背结论而是结合场景选数据结构。HashMap的题考了两个方向。第一个是遍历时能否直接删除元素。很多人会写for (Map.EntryString, Integer entry : map.entrySet()) { if (entry.getValue() 0) { map.remove(entry.getKey()); } }运行时会抛ConcurrentModificationException。正确的姿势是使用Iterator的remove()方法或者直接用map.entrySet().removeIf(...)。第二个是HashMap在JDK 8之后由“数组链表”升级为“数组链表红黑树”当链表长度超过8且数组长度大于等于64时会树化。为什么是8这是由泊松分布推出来的一个时间空间权衡结果。这类题对做业务开发的工程师来说不需要会推公式但至少要知道它不是一个无限往链表里塞的数据结构。Lambda和stream的题我特意把实用性和陷阱放在一起。比如考这么一道ListString list Arrays.asList(apple, banana, cherry); Collections.sort(list, Comparator.comparing(String::length).reversed());看起来没问题但实际运行会报错——因为Comparator.comparing的类型推断在这里会推不出String::length返回的是Integer还是其他类型需要显式加上类型Collections.sort(list, Comparator.comparing((String s) - s.length()).reversed());这种小坑在实际开发里特别多尤其是把Lambda和泛型放在一起的时候编译器经常变“谜语人”。另一个高频考点是把某元素排到第一位list.sort(Comparator.comparing((String s) - s.equals(target) ? 0 : 1) .thenComparing(Comparator.naturalOrder()));这种写法比手写两个条件比较器清晰很多而且方便扩展。说实话用了几年Lambda之后我几乎不会再写匿名内部类了Lambda的核心价值不是代码行数少了而是把行为作为参数传进来这件事变得无比自然这也是JDK 8给Java带来的最大变化。3. 亲手把代码跑起来练习卷的实操与编码题3.1 从安装JDK到配置环境变量的完整演示这套卷子专门安排了一道环境题给出一个命令javac -version和java -version输出不一致的情况让考生排查并修复。这道题的答对率比纯理论题低很多说明很多人根本没用命令行编译过Java。我先说一下标准环境的搭建思路。下载JDK之后需要配置三个环境变量JAVA_HOME指向JDK安装根目录PATH里新增%JAVA_HOME%\binWindows或$JAVA_HOME/binLinux/macOSCLASSPATH在JDK 9之后其实已经不强制要求配置了但对于还在用老教程的人来说可以让它指向.当前目录。配置完成后在命令行输入java -version如果显示的和JAVA_HOME指定的版本一致说明环境变量生效了。实操中最常见的问题有三个问题一环境变量配了但命令行不生效。这种情况十有八九是PATH里已经有了其他版本的JDK路径并且排在前面。Windows下可以用where java看看到底执行的是哪个Linux下用which java查看定位后再调整顺序。问题二改了环境变量后还是旧版本。因为命令行窗口缓存了环境变量需要重开一个终端窗口。如果是IDEA或Eclipse里跑需要重启IDE。问题三javac能编译但java运行时报“找不到或无法加载主类”。这个多半是因为不小心在类名后面加了.class后缀或者类的包名没对应上目录结构。初学者最容易踩的就是把带包名的类放到不带包名的目录里直接用java Main去跑正确做法是回到包的根目录执行java com.example.Main。这道实操题其实还隐含了一个价值现代IDE太强大导致很多人根本不知道javac和java到底做了什么。一旦你理解了编译和运行是两步后面看到mvn compile、mvn package就不慌因为这些本质上是把javac和jar这些命令打磨成了自动化的流水线。3.2 冒泡排序与快速排序的编码题精解编码题部分我安排了两个排序算法冒泡排序和快速排序。冒泡排序要求手写一个通用方法并说明它的时间复杂度。快速排序则给了一个半成品要求补全递归逻辑。冒泡排序的参考实现public static T extends ComparableT void bubbleSort(T[] arr) { if (arr null || arr.length 2) { return; } int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j].compareTo(arr[j 1]) 0) { T temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { break; } } }这里我故意考了几个点泛型方法怎么写、Comparable接口怎么用、优化点在于哪swapped标志位。冒泡排序最好的情况是数组已经有序时间复杂度为O(n)最坏和平均情况都是O(n^2)。这个算法在实际工程中几乎不会用到但它的价值在于入门理解“比较与交换”这两个基础动作。快速排序的补全题给了这样一个框架public static void quickSort(int[] arr, int low, int high) { if (low high) { int pivotIndex partition(arr, low, high); quickSort(arr, low, pivotIndex - 1); quickSort(arr, pivotIndex 1, high); } }要补全的是partition方法。经典写法是取最后一个元素为基准值用双指针把比基准小的换到左边private static int partition(int[] arr, int low, int high) { int pivot arr[high]; int i low - 1; for (int j low; j high; j) { if (arr[j] pivot) { i; int temp arr[i]; arr[i] arr[j]; arr[j] temp; } } int temp arr[i 1]; arr[i 1] arr[high]; arr[high] temp; return i 1; }快速排序的平均时间复杂度是O(n log n)最坏情况退化为O(n^2)。退化的原因通常是每次选的基准值恰好是最大或最小值导致分区极度不平衡。实际开发中很少自己写快排但理解“分治”思想对后面看ForkJoin、MapReduce以及各种分库分表方案都有帮助。3.3 接口自动化测试框架里的反射与动态代理思路这部分我给二星级卷子塞了一道稍微带点工程味道的题一个接口测试框架需要读取某个接口类的所有方法并自动生成Mock数据去调用。问应该用Java的哪个技术实现。答案是反射Reflection。具体步骤是Class? clazz YourApiClass.class; Method[] methods clazz.getDeclaredMethods(); for (Method method : methods) { String methodName method.getName(); Class?[] paramTypes method.getParameterTypes(); // 根据参数类型生成mock数据 // 通过method.invoke(targetObject, mockArgs)调用 }这里并不是让考生真的实现一个测试框架而是让他理解反射是框架与业务代码解耦的关键机制。Spring MVC里URL到Controller方法的映射靠的就是反射MyBatis里Mapper接口没有实现类却能执行SQL靠的是JDK动态代理加反射你在IDEA里写代码时的自动补全底层也有反射的身影。很多初学者对反射很恐惧觉得“这东西会影响性能”。实际上对于接口测试、配置加载这类低频操作反射完全够用。真正要注意的是反射可以绕过访问控制调用私有方法、修改私有字段这会让代码变得难以维护所以不是所有场景都适合反射。我给二星级卷子的要求是知道它能干什么会用原生写法处理一个对象的所有方法就够了。4. 练习卷里最常见的报错与排查实录4.1 Java: 源发行版17需要目标发行版17的解决方案几乎每次培训都会有人遇到这个报错“java: 源发行版 17 需要目标发行版 17”或者反过来“警告: 源发行版 17 需要目标发行版 17”。这套二星级卷子把它作为必考工程题之一因为太典型了。这个报错的本质是JDK版本、项目语言级别、编译目标版本三者不一致。在IDEA里你可能会看到项目SDK是JDK 17但Project Structure里的Language Level还是8或者Maven的pom.xml里的maven.compiler.source和target写的是1.8而本地默认的javac是17。这几种情况都会导致编译期报错或警告。排查步骤我总结成四步第一步确认java -version当前默认的JDK版本。第二步在IDEA中打开File - Project Structure - Project确认SDK例如17和Language Level例如17。第三步检查Modules里的每个模块的Language Level是否和Project一致。第四步检查pom.xml或build.gradle里是否显式指定了source/target如果有就改成与JDK匹配的版本。如果在命令行直接用javac编译还可以用参数控制javac -source 17 -target 17 YourFile.javaMaven项目中更推荐的是在pom.xml里统一配置properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties这个报错之所以常见是因为一个项目往往在多人协作时被不同的IDE自动重设了Language Level。解决之后我还建议顺手在.gitignore里把.idea/目录排除掉避免IDEA的配置随代码提交影响其他人。4.2 Lombok报错与Java内存不足的排查思路二星级卷子里还放了一个很捣乱的报错题“java: You arent using a compiler supported by lombok, so lombok will not work with your project.”这个报错通常出现在IDE升级了JDK或者项目里引入了新版本的Lombok但编译器插件版本不匹配的时候。Lombok的核心原理是注解处理器Annotation Processor它在编译期扫描带有Getter、Setter、Slf4j等注解的类然后生成对应的字节码。如果IDE内置的编译器版本和Lombok支持的版本不匹配就会报这个错。解决方法一般有以下几种升级Lombok版本在pom.xml里把版本改到最新然后重新导入Maven工程。检查IDE的Settings - Build, Execution, Deployment - Compiler - Annotation Processors确认“Enable annotation processing”是勾上的。如果是Maven命令行编译确认使用的是Maven自带的JDK而不是某个过旧的JDK。另一个实战中很常见的报错是“java: OutOfMemoryError: insufficient memory”或者“java.lang.OutOfMemoryError: Java heap space”。二星级卷子不会要求你去做完整的JVM调优但会要求至少能区分三类内存问题堆内存不足报Java heap space。常见原因是创建了大量对象没释放比如循环里不停new对象再放入集合。解决方式是调整堆大小-Xms512m -Xmx2g但根本解决方式是检查代码里有没有内存泄漏。栈溢出报StackOverflowError最常见的场景是无限递归。检查有没有缺了递归终止条件。元空间不足报Metaspace相关错误。常见于大量动态生成类的场景比如CGLIB代理、热加载可以通过-XX:MaxMetaspaceSize调整但更需要关注的是类加载器是否泄漏。我在带新人时最常强调的一个原则是遇到OutOfMemoryError不要第一反应就调大内存先找到谁吃了内存。二星级卷子里的内存题其实就是出一道填空题给一段写得很烂的代码让考生指出哪里最容易导致内存溢出。答案是“把一个不断增长的日志字符串拼接到static集合里”因为静态集合的生命周期跟类一样长永远不会被回收。4.3 VSCode运行Java报乱码及环境不一致问题现在用VSCode写Java的人越来越多不是因为VSCode比IDEA好用而是因为它轻量很多写微服务接口或者临时脚本的人不想开一个好几GB内存的IDE。但VSCode的乱码问题实在太高频了高频到我把“控制台输出中文乱码”作为二星级练习卷的一道送分题放进去。乱码的本质是编码不一致。Java源文件是UTF-8但Windows控制台默认用GBK或者反过来编译时javac如果没有指定编码就会按平台默认编码去读导致乱码。解决方法也很清晰在settings.json里把文件的默认编码设为UTF-8files.encoding: utf8。在launch.json里给运行配置加上VM参数vmArgs: -Dfile.encodingUTF-8。编译时显式指定编码javac -encoding UTF-8 YourFile.java。如果是Maven项目在pom.xml里设置项目属性properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties还有一个更容易忽略的问题VSCode自带终端和系统终端的编码可能不一致。我建议遇到乱码时先用chcp命令查看Windows控制台的当前代码页65001代表UTF-8936代表GBK。不乱码的目标就是让“文件编码、终端编码、JVM默认编码”三者统一。4.4 常见问题速查表为了便于复习我整理了一张二星级练习卷常见问题的速查表基本覆盖了开头提到的那批搜索热词里最典型的报错报错/问题根因快速处理建议源发行版 17 需要目标发行版 17项目Language Level / Maven compiler配置与JDK不一致统一JDK版本和编译配置全部改为17insufficient memoryJVM堆或元空间不足调-Xmx的同时查代码内存泄漏lombok will not workLombok版本与IDE编译器不匹配升级Lombok开启注解处理中文乱码源文件/终端/JVM编码不一致统一UTF-8控制台代码页65001ArrayIndexOutOfBoundsException循环边界或集合索引越界检查起点终点、subList边界、分页公式ConcurrentModificationException遍历集合时直接修改使用Iterator或removeIfStackOverflowError无限递归或栈帧过大检查递归终止条件合理拆分方法找不到或无法加载主类类名/包名/目录结构不匹配回到包根目录执行全限定类名这张表我在每次培训结束时都会发出去很多新同学的反馈是看到报错能快速对应到根因比在网上一条条搜效率高很多。5. 从二星级到进阶怎么用好这套练习卷5.1 自测与复盘的正确方法练习卷不是做完对个答案就结束的。我推荐一个三遍法第一遍按考试模式做不看资料不查文档做完之后给自己打分。这一遍的主要目的是暴露问题别怕错得多错得多说明自查的价值大。第二遍带着错题回到源码和官方文档去查。比如HashMap的树化问题直接打开HashMap.class看treeifyBin方法的注释Integer缓存问题直接看IntegerCache类的实现。这一遍会花很多时间但它才是真正把知识内化的过程。第三遍隔一周之后把错题重做一遍并且尝试用不同的写法完成同样的功能。比如第一遍用for循环遍历集合第二遍试着用stream和Lambda重写。这样做不是为了炫技而是让自己明白“同一个问题的不同解法在可读性、性能上的差异”。我见过太多人刷题就是“看题→看答案→假装会了→下次还是不会”。如果这套二星级卷子做完你能把每个错题都讲给别人听那它的效果就超过了市面上大多数视频课程。5.2 从练习卷延伸出的学习路线这套二星级卷子对应的Java学习路线我大概画一个轮廓给还在迷茫的新人参考第一阶段语法基础变量与运算符、流程控制、数组、方法调用、面向对象三大特性。学完能写一个带类和多对象的简单管理系统。第二阶段常用APIString、StringBuilder、集合框架、日期时间API、Optional。学完能用集合和Lambda处理一批真实数据。第三阶段异常与IO异常体系、文件读写、序列化、网络编程基础Socket。学完能把数据落盘、能对接一个简单的外部服务。第四阶段工程与框架Maven/Gradle、JUnit单元测试、MySQLJDBC、MyBatis、Spring Boot基础。学完能独立开发一个CRUD接口。第五阶段并发与JVM线程、锁、并发容器、JVM内存模型、GC基础。这一阶段更适合工作一年以后回头补。二星级卷子覆盖的是第一和第二阶段以及第三阶段的开头。如果你这套卷子能稳定做到85分以上就可以直接开始学Spring Boot不会因为听不懂“Bean是什么”而卡住。如果分数在60分以下建议不要急着上框架回头把基础再打磨一下否则后面会花好几倍时间在“报错—百度—还是不懂”的死循环里。5.3 分享一个我批改完练习卷之后的小技巧好几次批改卷子的时候我发现一个有趣的现象相同的一道题有人写出来的答案是“背的”有人写出来的答案是“理解过的”。比如问HashMap默认容量是多少背下来的人会答16理解过的人会补上一句“但当你传入初始容量时HashMap会把它调整为大于等于该值的2的幂次方比如传3就会变4传5就会变8”。后者才是工程师该有的状态。我特别建议每位拿到卷子的人在题目旁边做两件事一是给每个知识点写一个“我实际在哪里见过它”的例子二是给每个错题写一句“如果再遇到我会怎么排查”。别小看这两个动作它能把一套普通的练习卷变成自己的知识地图。我现在带团队面试候选人的时候很少直接问“八股文”而是拿这套卷子里的题目当引子不断追问“为什么”和“你遇到会怎么办”一问就能把真正理解的人挑出来。如果你刚入行或者准备跳槽我建议你给自己一个下午的时间安安静静把这份二星级练习卷做完。不要怕基础题丢人——这些基础题丢的分比任何高深的框架题丢的分都更能反映你真实的开发水平。把这里面的知识消化掉再去碰并发、JVM调优、设计模式这些进阶内容你会发现原来那些“很玄”的东西都是基础知识的堆叠而已。
返回列表