做了这么多年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.Entry<String, 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的题我特意把实用性和陷阱放在一起。比如考这么一道:
List<String> 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%\bin(Windows)或$JAVA_HOME/bin(Linux/macOS),CLASSPATH在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 Comparable<T>> 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.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>这个报错之所以常见,是因为一个项目往往在多人协作时被不同的IDE自动重设了Language Level。解决之后,我还建议顺手在.gitignore里把.idea/目录排除掉,避免IDEA的配置随代码提交影响其他人。
4.2 Lombok报错与Java内存不足的排查思路
二星级卷子里还放了一个很捣乱的报错题:“java: You aren't 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-8:"files.encoding": "utf8"。 - 在
launch.json里给运行配置加上VM参数:"vmArgs": "-Dfile.encoding=UTF-8"。 - 编译时显式指定编码:
javac -encoding UTF-8 YourFile.java。 - 如果是Maven项目,在
pom.xml里设置项目属性:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>还有一个更容易忽略的问题:VSCode自带终端和系统终端的编码可能不一致。我建议遇到乱码时,先用chcp命令查看Windows控制台的当前代码页,65001代表UTF-8,936代表GBK。不乱码的目标就是让“文件编码、终端编码、JVM默认编码”三者统一。
4.4 常见问题速查表
为了便于复习,我整理了一张二星级练习卷常见问题的速查表,基本覆盖了开头提到的那批搜索热词里最典型的报错:
| 报错/问题 | 根因 | 快速处理建议 |
|---|---|---|
源发行版 17 需要目标发行版 17 | 项目Language Level / Maven compiler配置与JDK不一致 | 统一JDK版本和编译配置,全部改为17 |
insufficient memory | JVM堆或元空间不足 | 调-Xmx的同时查代码内存泄漏 |
lombok will not work | Lombok版本与IDE编译器不匹配 | 升级Lombok,开启注解处理 |
中文乱码 | 源文件/终端/JVM编码不一致 | 统一UTF-8,控制台代码页65001 |
ArrayIndexOutOfBoundsException | 循环边界或集合索引越界 | 检查起点终点、subList边界、分页公式 |
ConcurrentModificationException | 遍历集合时直接修改 | 使用Iterator或removeIf |
StackOverflowError | 无限递归或栈帧过大 | 检查递归终止条件,合理拆分方法 |
找不到或无法加载主类 | 类名/包名/目录结构不匹配 | 回到包根目录执行全限定类名 |
这张表我在每次培训结束时都会发出去,很多新同学的反馈是:看到报错能快速对应到根因,比在网上一条条搜效率高很多。
5. 从二星级到进阶:怎么用好这套练习卷
5.1 自测与复盘的正确方法
练习卷不是做完对个答案就结束的。我推荐一个三遍法:
第一遍,按考试模式做,不看资料不查文档,做完之后给自己打分。这一遍的主要目的是暴露问题,别怕错得多,错得多说明自查的价值大。
第二遍,带着错题回到源码和官方文档去查。比如HashMap的树化问题,直接打开HashMap.class看treeifyBin方法的注释;Integer缓存问题,直接看IntegerCache类的实现。这一遍会花很多时间,但它才是真正把知识内化的过程。
第三遍,隔一周之后把错题重做一遍,并且尝试用不同的写法完成同样的功能。比如第一遍用for循环遍历集合,第二遍试着用stream和Lambda重写。这样做不是为了炫技,而是让自己明白“同一个问题的不同解法在可读性、性能上的差异”。
我见过太多人刷题就是“看题→看答案→假装会了→下次还是不会”。如果这套二星级卷子做完,你能把每个错题都讲给别人听,那它的效果就超过了市面上大多数视频课程。
5.2 从练习卷延伸出的学习路线
这套二星级卷子对应的Java学习路线,我大概画一个轮廓,给还在迷茫的新人参考:
- 第一阶段(语法基础):变量与运算符、流程控制、数组、方法调用、面向对象三大特性。学完能写一个带类和多对象的简单管理系统。
- 第二阶段(常用API):
String、StringBuilder、集合框架、日期时间API、Optional。学完能用集合和Lambda处理一批真实数据。 - 第三阶段(异常与IO):异常体系、文件读写、序列化、网络编程基础(Socket)。学完能把数据落盘、能对接一个简单的外部服务。
- 第四阶段(工程与框架):Maven/Gradle、JUnit单元测试、MySQL+JDBC、MyBatis、Spring Boot基础。学完能独立开发一个CRUD接口。
- 第五阶段(并发与JVM):线程、锁、并发容器、JVM内存模型、GC基础。这一阶段更适合工作一年以后回头补。
二星级卷子覆盖的是第一和第二阶段,以及第三阶段的开头。如果你这套卷子能稳定做到85分以上,就可以直接开始学Spring Boot,不会因为听不懂“Bean是什么”而卡住。如果分数在60分以下,建议不要急着上框架,回头把基础再打磨一下,否则后面会花好几倍时间在“报错—百度—还是不懂”的死循环里。
5.3 分享一个我批改完练习卷之后的小技巧
好几次批改卷子的时候,我发现一个有趣的现象:相同的一道题,有人写出来的答案是“背的”,有人写出来的答案是“理解过的”。比如问HashMap默认容量是多少,背下来的人会答16,理解过的人会补上一句“但当你传入初始容量时,HashMap会把它调整为大于等于该值的2的幂次方,比如传3就会变4,传5就会变8”。后者才是工程师该有的状态。
我特别建议每位拿到卷子的人,在题目旁边做两件事:一是给每个知识点写一个“我实际在哪里见过它”的例子,二是给每个错题写一句“如果再遇到,我会怎么排查”。别小看这两个动作,它能把一套普通的练习卷变成自己的知识地图。我现在带团队面试候选人的时候,很少直接问“八股文”,而是拿这套卷子里的题目当引子,不断追问“为什么”和“你遇到会怎么办”,一问就能把真正理解的人挑出来。
如果你刚入行或者准备跳槽,我建议你给自己一个下午的时间,安安静静把这份二星级练习卷做完。不要怕基础题丢人——这些基础题丢的分,比任何高深的框架题丢的分都更能反映你真实的开发水平。把这里面的知识消化掉,再去碰并发、JVM调优、设计模式这些进阶内容,你会发现原来那些“很玄”的东西,都是基础知识的堆叠而已。