
1. 你该不该做这份Java测验先聊聊我的出发点最近在带几个刚转行学Java的朋友发现一个特别普遍的问题视频看了一堆代码跟着敲了无数遍一到合上书自己写或者被问到某个基础知识的时候脑子里就开始模糊。尤其是那种看着都认识、一考就错的知识点比如String和StringBuilder的区别、和equals到底什么时候相等、HashMap扩容之后链表什么时候转红黑树……全挤在一团。所以我就整理了一套Java测验5——其实是我自己给第五轮阶段自测起的名字。这套测验不是学校期末考那种死记硬背题而是围绕Java基础核心知识点、高频面试题和真实开发中容易踩坑的细节设计的。做完这套题你基本能摸清自己处在哪个水平是刚入门的会敲代码还是已经开始理解为什么要这么写的阶段。我把它分成几个部分基础语法与运算符、面向对象、常用类与集合、异常处理、还有几道综合编程题。每道题我都配了解析和踩坑提醒顺便把背后涉及的面试追问也列了出来。不管你是在校学生、准备跳槽的初级开发还是自学Java想找个自测方向的人这套测验都适用。需要提前说明的是我自己设计这套题的时候刻意避开了一些偏题怪题。Java面试八股文里确实有很多刁钻的问题但作为阶段自测我更推荐先扎扎实实把底层逻辑搞明白再去背那些奇技淫巧。毕竟靠背出来的八股面试官多追问两句就露馅了。2. 题目覆盖思路为什么考这些不考那些2.1 核心考点选择逻辑如果你去看市面上的Java基础面试题大全动辄几百道但其实高频精华也就那么几十个方向。我在设计Java测验5的时候遵循了三个选考点原则第一高频使用原则。凡是日常开发中每天都要见面的知识必须考。比如运算符优先级、循环控制、面向对象三大特性、集合框架的选型、异常处理机制。这些不是八股文是基本功。第二易混淆原则。很多Java知识点单独拎出来谁都会放在一起对比就容易乱。典型如int和Integer的自动装箱比较、ArrayList和LinkedList的适用场景、重载和重写的区别。这些恰恰是面试官最爱挖坑的地方。第三底层原理原则。不要求你背出JVM源码但要能说出为什么。为什么HashMap是无序的为什么String不可变为什么lambda表达式要求变量是 effectively final 这些为什么才是区分初级和中级的门槛。2.2 我故意没放进去的内容有一种题我基本不出纯背书型题目。比如Java的8种基本数据类型是什么——这种题你上网搜一下全有考了没有区分度。我更愿意出一道short s1 1; s1 s1 1; 和 s1 1; 有什么区别这样的变体表面上考类型转换实际上考运算符重载和隐式类型转换这种题才有意思。另外我也不建议在这个阶段纠结一些过于底层的八股比如JVM内存模型中对象头占多少字节、volatile的指令重排序细节。不是说这些不重要而是基础语法还没打通的人学这些只能靠背背完就忘。等你的代码量上去之后回头再理解这些会轻松得多。2.3 这份测验适合什么人我的建议是你至少已经学完Java SE的核心内容或者正在准备Java初级/实习岗位面试或打算系统地检查一遍自己有没有知识盲区。如果刚学到循环语句可以先跳过后面的集合和异常部分。我自己给朋友的用法是周末花一个小时做完整套题对答案错题全部标记出来下周针对性地补。3. Java测验5全卷解析题目、答案与考点细说3.1 基础语法与运算符专项这部分是热身题但热身不代表简单。很多老手在这里也会翻车。题1下面代码运行后输出结果是什么int i 0; i i i; System.out.println(i);解析这个题是老演员了几乎每一套Java面试题里都有它。核心考的是i和i的本质区别一个先返回后自增一个先自增后返回。计算过程拆开看——左侧的i先取原值0参与计算i变成1右侧的i先自增i从1变成2再取2参与计算所以结果是022。我见过不止一个干了两年Java的人在这里答错说明平时写代码基本不会用这种写法。实操建议写业务代码的时候千万不要把i和i混在同一个表达式里。你自己可能知道执行顺序但读你代码的同事未必能在3秒内反应过来。好的代码应该是一眼就能看懂而不是推理五分钟才能确认。强行秀操作review的时候大概率会被骂。题2以下哪些表达式的值是true多选A. abc abc B. new String(abc) abc C. a bc abc D. new String(abc).equals(abc)解析这题是我眼里覆盖面最广的一道基础题因为它同时考了字符串常量池、编译期常量折叠、equals和的区别。A选项两个字符串字面量在常量池中引用同一个对象所以是true。B选项new出来的对象在堆上引用地址不同是false。C选项abc在编译期就能确定结果是abc编译器会直接折叠成字符串字面量所以和abc是同一个常量池引用true。D选项equals比较的是内容自然是true。实战经验很多面试官会把这道题变形成String s1 new String(abc); String s2 s1.intern(); s1 s2来问。这里的核心是intern()方法会在常量池里查找或创建相同内容的字符串并返回常量池引用。所以s1是堆上对象引用s2是常量池引用两者不相等。这种追问逻辑你要是能当场画图说明面试官印象分会高很多。题3下面代码哪里有问题short s1 1; s1 s1 1;解析经典陷阱题。s1 1的结果是int类型把int赋值给short需要强制类型转换所以编译报错。如果改成s1 1就可以因为复合赋值运算符自带隐含强转。这道题的变体是问s1 s1 1和s1 1在什么极端情况下结果不同——答案是当s1 1的结果超出short范围时s1 1会发生精度丢失但不报编译错误而如果你强制转换也会丢失。所以更严谨的说法是复合赋值运算符隐含强转可能导致数据溢出这是它的副作用。3.2 面向对象核心机制面向对象是Java的根也是面试八股文的重灾区。我的出题思路是避开什么是封装/继承/多态这种背课本的题直接上代码场景。题4下面代码的输出顺序是什么class Parent { static { System.out.print(1 ); } public Parent() { System.out.print(2 ); } { System.out.print(3 ); } } class Child extends Parent { static { System.out.print(4 ); } public Child() { System.out.print(5 ); } { System.out.print(6 ); } } public class Test { public static void main(String[] args) { new Child(); } }解析这题考的是类加载和实例化时的初始化顺序属于那种看着简单一写就错的题。正确输出是 1 4 3 2 6 5。规律总结成口诀就是先静态父类静态块优先于子类静态块再父类实例化父类构造代码块先于父类构造方法最后子类实例化子类构造代码块先于子类构造方法。我踩过的坑有一次真在项目里遇到一个诡异Bug——父类构造方法里调用了一个被子类重写的方法结果子类字段还没被初始化读出来是null。这就是构造方法中不要调用可重写方法这条铁律的来源。如果父类构造器在子类字段初始化之前调用了一个动态绑定方法得到的结果一定不是你期望的。排查这个Bug花了我们半个下午从那以后我写代码坚决不在构造方法里做动态绑定调用。题5抽象类和接口下面说法正确的是A. 抽象类可以没有抽象方法 B. 接口可以包含成员变量 C. 一个类可以实现多个接口但只能继承一个类 D. 接口方法默认都是public abstract的解析正确答案是A和C。A选项是对的一个类即使没有抽象方法也可以声明为abstract目的就是防止被实例化B选项接口可以定义成员变量但默认是public static final的也就是常量这跟成员变量的语义有区别C选项Java单继承多实现没问题D选项Java 8之后接口可以有default和static方法不能说默认都是这个说法过时了。关于接口和抽象类的选择我的个人经验是能用接口表达能做什么就不要用抽象类表达是什么。除非你确实有一批子类共享了公共状态字段否则优先接口。组合优于继承这是我从设计模式里学到的最大教训之一。3.3 集合框架与泛型集合是Java面试必考模块。我在这里设计了两个题一个是HashMap的基础机制一个是集合排序。题6关于HashMap下面哪个说法是错误的A. HashMap允许key和value为null B. HashMap线程不安全 C. HashMap在JDK 8之后使用数组链表红黑树 D. HashMap的默认初始容量是16加载因子是0.75扩容时翻倍 E. HashMap的put方法在key冲突时新值会放到链表头部解析错误的是E。JDK 8之后HashMap链表插入元素采用尾插法也就是新元素追加到链表尾部。JDK 7及以前是头插法。为什么改成尾插法因为头插法在并发扩容时会造成链表环导致get死循环。虽然HashMap本来就不保证线程安全但JDK团队还是顺手把这个问题修了。这道题能考察你是不是停留在背结论的层面真的理解过HashMap的演进逻辑。题7使用Comparator.comparing对列表排序把指定元素排到最前面。这个题很有实战价值。我问过几个工作两三年的朋友第一反应都是先排序再把指定元素移出来放到头部这当然可以但不够优雅。我给的参考实现是ListString list Arrays.asList(apple, banana, cherry, date); String first cherry; list.sort(Comparator .comparing((String s) - s.equals(first)) .reversed() .thenComparing(Comparator.naturalOrder()));这段代码的逻辑是先把是否等于first这个布尔值排序布尔值false(0)排在true(1)前面所以reversed()之后true排前面然后其余元素再按字母序排。代码量少也不破坏原数据结构。不过要注意Comparator.comparing的泛型推断偶尔会出问题所以第一个lambda的参数类型我显式写成了(String s)这是处理泛型推断的常用技巧。3.4 异常与常见运行时错误异常这块我想出一个动手纠错型的题比背Error和Exception的区别更实用。题8下面代码有什么问题和隐患try { ListInteger list new ArrayList(); while (true) { list.add(1); } } catch (Exception e) { e.printStackTrace(); }解析你永远也不可能在这个程序里捕获到任何异常因为它抛出的是OutOfMemoryError不是Exception。OutOfMemoryError是Error的子类只有Throwable才能接住它。用catch(Exception)捕获不到。这个题在热搜词里也出现了就是java: outofmemoryerror: insufficient memory。真实开发里遇到OOM正确的做法是先通过jmap等工具把堆内存dump下来再用MAT分析是哪个对象占了大头而不是简单加-Xmx参数。我遇到过一些团队一遇到OOM就无脑调大内存结果内存加到32G照样OOM最后才发现是代码里有死循环在疯狂往List里塞数据——就像我出的这道题一样。3.5 编程实操题到了综合编程题我的要求是不但要写对还要说清楚为什么这么写。下面这道题直接对标初级开发岗的笔试题。题9写一个方法判断一个字符串是否是回文字符串忽略大小写和非字母数字字符。public static boolean isPalindrome(String s) { int left 0, right s.length() - 1; while (left right) { char lc s.charAt(left); char rc s.charAt(right); if (!Character.isLetterOrDigit(lc)) { left; } else if (!Character.isLetterOrDigit(rc)) { right--; } else { if (Character.toLowerCase(lc) ! Character.toLowerCase(rc)) { return false; } left; right--; } } return true; }解析这道题考三个点双指针思路、Character工具类的使用、对大小写和非字母数字字符的处理。很多人第一反应是用正则表达式s.replaceAll([^A-Za-z0-9], ).toLowerCase()然后逐字符对比这个方案功能上没问题但会多一次O(n)的遍历和一次String对象创建。双指针一次遍历搞定空间复杂度O(1)。在笔试场景里双指针是更优解。3.6 附加题Java 8新特性与常见编译问题最后加一道lambda和编译相关的题原因是这两个方向是很多自学者的痛点。我见的报错太多了什么vscode运行java报错乱码、java: 警告: 源发行版 17 需要目标发行版 17、java: you arent using a compiler supported by lombok全都是环境配置问题不是代码问题。题10关于lambda表达式下面哪种写法是正确的A. ConsumerString c s - System.out.println(s); B. Runnable r () - System.out.println(run); C. BiFunctionInteger, Integer, Integer f (a, b) - a b; D. SupplierInteger sup () - 42;解析四个选项全部正确。但为什么我要出这道题因为我发现很多人其实分不清这几个函数式接口的适用场景。简单记法Consumer消费一个参数不返回、Supplier不接收参数但返回一个值、Function接收一个参数返回一个值、BiFunction接收两个参数返回一个值、Predicate接收参数返回boolean。lambda表达式的类型推断是编译期完成的不是运行期所以如果你到处写(Object a, Object b) - a.equals(b)一旦两个参数不是同一个类型编译期就会报错。题11关于lombok编译警告you arent using a compiler supported by lombok, so lombok will not work这个我在热搜里看到有朋友遇到顺手聊一下。出现这个警告绝大多数情况下是你项目的编译器和IDE内置的lombok版本不匹配常见的比如用的JDK 22但lombok版本还停留在1.18.30以下。解决办法是升级lombok依赖到最新版同时确保IDE的annotation processing是开启的。还有一个冷门原因是项目里混用了不同版本的lombok依赖Maven依赖树里出现了多个lombok这种建议用mvn dependency:tree排查。题12vscode运行Java报错乱码典型的编码问题。Windows下默认GBK编码而VSCode默认UTF-8控制台输出中文就会乱。解决方案有两个一是在launch.json里加上vmArgs: -Dfile.encodingUTF-8二是改系统的环境变量JAVA_TOOL_OPTIONS-Dfile.encodingUTF-8但推荐第一种影响范围可控。4. 从测验里挖出来的高频面试追问背会这些面试不慌做完上面这一套题你会发现有不少知识点面试官是可以连环追问的。我把自己在面试候选人时最爱问的追问链整理出来方便你自查题目常见追问链String常量池追问1intern()方法的作用追问2new String(abc)创建了几个对象追问3String为什么设计成不可变HashMap追问1链表转红黑树的阈值为什么是8追问2为什么加载因子是0.75追问3HashMap在多线程下会有什么问题追问4ConcurrentHashMap的实现原理自动装箱追问1Integer缓存范围是多少追问2为什么是-128到127追问3Integer a 100; Integer b 100; a b结果是异常追问1Error和Exception的区别追问2受检异常和非受检异常的区别追问3有没有用过自定义异常定义的时候继承了哪个类lambda追问1lambda捕获的局部变量为什么必须是effectively final追问2Function和UnaryOperator有什么区别追问3stream的并行流parallelStream有什么问题这里挑几个重点展开讲讲因为这些追问背后才是真正拉开差距的地方。String不可变性的价值三方面安全、线程安全、字符串常量池。安全是因为String经常被用作参数或配置项如果可变传入方法后可能被篡改线程安全是因为不可变对象天然线程安全常量池复用是因为不可变才能安全地被多个引用共享。另外哈希缓存也是一个隐藏好处——String的hashCode被缓存了因为值不会变所以在HashMap里作为key效率极高。HashMap加载因子为什么是0.75这是一个时间和空间的折中。加载因子越小比如0.5空间浪费严重加载因子越大比如1.0链表会很长查询效率变低。0.75是JDK团队通过统计学泊松分布计算出来的一个相对平衡值。至于链表转红黑树的阈值8也和泊松分布有关在加载因子0.75的前提下链表中节点数量达到8的概率是极低的约千万分之六所以用8作为阈值是合理的设计。Integer的缓存范围为什么是-128到127因为-128到127是使用最频繁的整数范围JVM启动时直接把这些包装对象创建好放入缓存用的时候直接返回引用。如果超过这个范围Integer a 200; Integer b 200; a b就是false了。我见过有人考这个题直接问new Integer(100)和Integer.valueOf(100)有什么区别——valueOf会走缓存new一定创建新对象。这就是为什么我们写代码时更推荐valueOf或自动装箱而不是new Integer。stream的parallelStream有什么坑并行流的底层是ForkJoinPool使用的是公共线程池。如果你的任务里有阻塞操作比如IO调用、数据库查询会占满公共池的线程影响整个JVM里其他并行流的执行。所以只有CPU密集型、无状态、无阻塞的任务才适合用parallelStream否则老老实实用串行流。我见过有同学在接口里用parallelStream批量调外部API结果把线程池打满导致整个应用变卡排查了半天才定位到。5. 常见问题与排查技巧实录做题之外的实战避坑5.1 编译环境的常见报错做这套测验的时候很多同学会在环境上先卡住。我把高频报错和解决方法列出来你直接照着操作就行。报错信息原因解决方案java: 警告: 源发行版 17 需要目标发行版 17IDE编译器的java版本低于项目配置版本在IDE里检查Project Structure把SDK和language level统一Maven项目检查pom.xml里maven-compiler-plugin的source/targetjava: internal error in the mapping processor: java.lang.NullPointerException常见于MapStruct在JDK高版本下编译异常升级mapstruct-processor到1.5.5至少要适配JDK 21或者检查是否有属性名不匹配java: you arent using a compiler supported by lomboklombok版本和JDK不兼容升级lombok版本或者确认IDE的annotation processing已开启vscode运行java报错乱码控制台编码和源码编码不一致launch.json配置vmArgs: -Dfile.encodingUTF-8java.lang.ArrayIndexOutOfBoundsException数组越界检查循环边界建议用增强for循环或Arrays工具类5.2 环境变量配置这个老大难热搜词里java环境变量配置一直居高不下说明很多新手第一步就没走顺。我分享一个我自己教别人配置环境变量的思路特别简单先安装JDK比如JDK 17然后配置三个变量——JAVA_HOME指向JDK安装目录Path添加%JAVA_HOME%\binWindows下然后配置CLASSPATH为.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar其实高版本JDK不太需要手配CLASSPATH了。配置完在命令行输入java -version验证。最容易踩的坑装了多个JDK版本系统Path里既有JDK 8的路径又有JDK 17的路径顺序不同导致java版本不确定。排查方式是在命令行输入where javaWindows或which javaMac/Linux看实际解析到的是哪个路径。这个问题在热搜词里反复出现我完全不意外因为几乎所有新手都会装错。5.3 这份测验我自己做了一遍的感受我自己写完这套题顺手做了一遍本来以为自己闭着眼都能全对结果还是有题目愣了一下——第3题的short类型转换因为平时写代码都是用intshort类型确实很少用隔了半年再做容易反应不过来。这说明什么哪怕你是一个熟练开发者不常用的知识点也会生疏。这也是我建议大家定期做这种基础测验的原因它不是考你的智商而是帮你的大脑定期刷新这些底层知识点。5.4 如何判断这道题该不该深究我做技术问答经常被问到一个问题这个知识点我看到过但感觉太深了要不要花时间学我的判断标准很简单看它能不能被日常业务代码或两次以内面试追问覆盖。比如HashMap的红黑树结构——日常代码你用不到红黑树但面试一定会问而且会追问为什么是8这种就值得学。而JVM的G1垃圾回收器分代GC的具体细节如果你不是做性能优化的完全可以等用到的时候再深入。学习要有优先级先学生产力最高的知识。6. 最后的建议做完这套测验接下来怎么学这套Java测验5覆盖的是Java基础的核心骨架但绝不代表全部。如果你做完全卷感觉大部分题有把握下一步我建议按照这几个方向继续第一个方向是集合框架源码。别急着看数据结构课本直接把ArrayList、LinkedList、HashMap、ConcurrentHashMap的JDK源码读一遍。我第一次读HashMap源码的时候花了整整两天但读完之后很多原本要靠记忆的面试题突然就通了。比如为什么HashMap的容量总是2的幂次方因为(n - 1) hash这个位运算可以直接替代取模运算效率高得多。第二个方向是JVM基础。重点看内存区域划分、类加载机制、垃圾回收算法。特别是垃圾回收建议配合可视化工具JVisualVM或G1GC的日志分析来学光看概念效率太低。我当时是在自己的电脑上写了一个死循环生成对象的程序然后观察堆内存的变化曲线印象特别深刻。第三个方向是并发编程。synchronized和ReentrantLock的区别、volatile的语义、ThreadLocal的原理、线程池的核心参数。这个模块的面试高频程度不亚于集合框架而且特别容易出现看着简单、一深挖就答不上来的情况。第四个方向是框架和工具链。Spring Boot MyBatis基本是Java后端岗位的标配Maven/Gradle构建工具也得熟练。如果你是纯自学者建议用Spring Boot写一个简单的CRUD接口部署到本地完整走一遍开发流程这一步能帮你把前面学的所有基础知识串起来。我个人建议是按这个顺序推进不要跳步。这一步扩展其实是很多人忽略的学完基础语法就急着上Spring Boot结果出了问题连日志都不会看就是地基没打牢。最后再说一个小技巧做这种基础测验不要开卷不要边看解析边做做完再统一对答案。错了的题不只是看答案还要自己在IDE里把代码跑一遍打断点看执行过程。看到变量真实的变化比背一百遍解析都有用。祝做题顺利有问题欢迎留言交流我看到都会回复。