资讯动态

Java基础面试十问十答:String、HashMap、equals全覆盖

发布时间:2026/10/13 11:26:04 来源:尧图企业网站定制
这一期又到了十问十答。前几篇留言里不少人希望我把题目集中一下专门出一期覆盖字符串、集合、面向对象这些面试必问板块的。这期就干这件事选出的十道题全部是 Java 基础面试里出现频率最高的那一批不是让各位背标准答案而是把每道题的考察意图、回答思路、追问方向拆开讲清楚顺带放一些我自己的作答模板和踩坑提醒。题目本身不新但很多人挂在“背了答案却接不住追问”。比如 String 的不可变性能答上来一问到 intern() 就卡壳HashMap 的原理背得滚瓜烂熟一问为什么阈值是 8 就支支吾吾。这期内容尽量把这些“为什么”也覆盖到适合正在准备校招、跳槽面试的朋友也适合刚工作不久想系统梳理基础的人。1. 这期问题的编排逻辑1.1 我为什么选这十道题十道题看着多实际只分成了四个板块相等性判断、字符串、面向对象与异常、集合框架。这个顺序不是随手拍的是模拟真实面试节奏来的。面试官开场通常会先扔一个简单的比如“说说 和 equals 的区别”你答完了他立刻追问“那 hashCode 和 equals 的关系呢”这就自然进入第二题。字符串三连问同样是高频开场题尤其“String 创建对象”这道题几乎每家都会以不同形式出现。面向对象和异常部分考察的是编码习惯答得好不好一眼能看出是真写过代码还是纯背题。集合框架则是 Java 基础面试的压轴菜ArrayList 和 HashMap 各占一问属于必须吃透的硬货。这种由浅入深、从单一知识点发散到关联知识点的编排和真实面试的追问节奏一致。所以这期内容也建议按顺序看先理解单点再注意我标注的追问方向。1.2 拿到这份清单后怎么用我不太建议上来就背我的回答。那样面试官换个问法或者往里多追问一层你就容易露出痕迹。正确的用法是先自己尝试回答每一问想不起来再看答案重点看每个问题后面单独标注的“踩坑提醒”和“追问预警”部分这些才是面试官真正想试探你的地方。然后找一个基础尚可的朋友或者对着镜子模拟面试把每道题讲一遍要求讲得让没有 Java 基础的人也能听懂大概。能讲给别人听才说明你是真理解了而不是靠短期记忆堆出来的。我也会在最后单独列一节专门讲面试官顺着这十道题还会往哪些方向追问以及我实际面试和当面试官时看到的一些现场发挥问题。2. 相等性判断老八股里的第一个深坑2.1 第1问 和 equals 到底在比什么这一问基本是 Java 基础面试的开场白考察点非常明确你分不分得清引用比较和值比较。标准回答是这样 如果比较的是基本数据类型比较的是变量值本身是否相等如果比较的是引用类型比较的是两个引用是否指向堆内存里的同一个对象也就是地址是否相同。equals 是 Object 类里的一个方法Object 默认实现使用 比较地址但很多类比如 String、Integer 都重写了 equals用来比较对象的内容是否相等。面试官听完通常不会罢休会紧跟一个经典例子String a hello; String b hello; String c new String(hello); System.out.println(a b); // true System.out.println(a c); // false System.out.println(a.equals(c)); // truea 和 b 都在字符串常量池里指向同一个对象所以 为 true。c 是 new 出来的在堆内存中单独搞了一个对象虽然内容和 a 相同但地址不同所以 为 false。equals 因为 String 重写过比较的是字符内容所以返回 true。踩坑提醒Integer 也有类似的坑。Integer 会缓存 -128 到 127 之间的对象只要在这个范围内用 Integer x 100 这种写法两个 100 的 比较是 true一旦超过 127 就是 false。实际开发中判断两个 Integer 是否相等一定不要用 老老实实用 equals。很多线上问题就是因为拿 去比较包装类型结果在临界值附近偶发出现特别难排查。2.2 第2问为什么重写 equals 必须拉上 hashCode上一问答得顺利面试官立刻加码。这一问看似在问约定其实考察的是你对哈希集合底层原理的理解程度。先说规范本身Java 官方约定两个对象通过 equals 判断相等那么它们的 hashCode 也必须相等反过来hashCode 相等不代表 equals 相等哈希碰撞就是这种情况。如果你重写了 equals 却不动 hashCode就等于破坏了这份约定。真正开发中出现问题的地方在 HashSet、HashMap 这类基于哈希的集合上。它们的查找逻辑是先用 hashCode 定位到哈希桶再用 equals 比较桶内对象。如果两个本来相等的对象 hashCode 不同它们就会被分到不同的桶里后面即使 equals 返回 trueHashMap 也根本不会拿它们做比较于是出现“同一个 key 在集合里存了两份”的离谱现象。回答时可以带上这段代码说明你已经踩过或者至少理解这个坑public class User { private String id; public boolean equals(Object o) { if (this o) return true; if (o null || getClass() ! o.getClass()) return false; User user (User) o; return Objects.equals(id, user.id); } public int hashCode() { return Objects.hash(id); } }重写 hashCode 的黄金规则是用什么字段判断 equals就用什么字段参与哈希值计算。我见过很多人用 Objects.hash(id, name) 算哈希equals 里却只比较了 id结果 id 相同 name 不同的对象equals 为 true 但 hashCode 不同照样出问题。追问预警面试官接下来很可能让你说说为什么 HashMap 先比较 hashCode 再比较 equals。这就是为了效率先通过哈希定位到底层数组的某个桶把 O(n) 的查找缩小到 O(1)。如果不重写 hashCode即使 equals 相等HashMap 也无法快速找到对应条目。3. 字符串专题会背答案不等于会答面试题3.1 第3问String 不可变new 两次会不会多出一个对象这个问题的完整问法通常是“String str new String(abc) 一共创建了几个对象”我几乎能确定半数人第一次听到这个问题时都会答错。标准回答要把两种情况讲清楚。如果字符串常量池中已经有了“abc”那这句代码只会在堆内存中创建一个 String 对象这个对象的内部引用指向常量池里已有的“abc”。如果常量池中还没有“abc”那么会创建两个对象一个是常量池中的字面量对象一个是堆中的 String 对象同时栈上的引用 str 指向堆中的那个对象。注意这里的“一个对象”指的是一个实际的字符串实例不是指 JVM 里的引用。面试时最好主动说明这一点能显得你理解得比较准确而不是背了一套含糊的说法。顺便把 intern() 也准备好。调用 intern 方法时JVM 会检查常量池中是否已有相同内容没有就把它放进去有就返回常量池里的引用。所以下面这两句代码在实际开发里经常被拿去考人String a new String(abc).intern(); String b abc; System.out.println(a b); // true因为 intern 后 a 指向常量池对象至于 String 为什么要设计成不可变至少要能说出三点不可变对象天然线程安全多个线程可以共享同一个 String 实例而不需要同步方便字符串常量池缓存只有在不可变的前提下才能放心复用类加载器、网络协议参数等大量使用 String不可变性可以避免被意外篡改安全性更好。实操心法很多线上拼接日志或者动态 SQL 的场景经常会有人随手写一堆字符串相加。如果是个循环那问题会非常明显。我在实际项目里见过一次性能排查日志量一大GC 明显变频繁排查了一圈才发现是某段逻辑里循环拼接字符串每次都 new 出新的字符串对象。这种地方改用 StringBuilder 之后GC 压力立刻降下来。3.2 第4问字符串拼接到底该选谁的题这一问通常是接在第三问后面问 StringBuilder 和 StringBuffer 的区别顺带测试你知不知道什么时候用哪个。标准答法StringBuffer 是 JDK 1.0 就有的StringBuilder 是 JDK 1.5 加的。两者 API 几乎一样最大区别是 StringBuffer 的关键方法用 synchronized 修饰是线程安全的StringBuilder 没有加锁线程不安全但单线程环境下性能更好。所以线程安全要求比如类变量、跨线程共享时就选 StringBuffer局部变量拼接推荐 StringBuilder。面试官一般会接着问“那 String 的 拼接呢”。要答出这几点编译期间的字符串常量拼接比如 a b编译期就会直接变成 ab不会产生额外对象但如果是变量拼接Java 编译器在底层会创建一个 StringBuilder 对象来完成拼接。所以在循环体里写 str item相当于每轮循环都新建一个 StringBuilder执行完再转回 String效率很差应该手动把 StringBuilder 提到循环外面。开发中我自己的习惯是只要不是跨线程共享的场景统一用 StringBuilder。StringBuffer 没必要为了“可能多线程”这个模糊理由去用因为它的锁保护范围其实很有限真有并发写入需求时往往需要更高的方案。如果你在代码 review 里看到有人在一个局部循环里反复 new StringBuilder直接提醒他提出来复用就行。4. 面向对象与异常考察的是编码习惯4.1 第5问重载与重写除了背区别还要知道什么重载和重写的区别是标准老题但面试官的关注点往往不在区别表本身而在你后续能不能说出“编译期多态”和“运行期多态”这两个概念。标准答法重载是发生在同一个类中方法名相同参数列表不同返回类型可以不同也可以相同它不依赖继承关系是编译期根据参数类型决定调用哪个方法所以叫静态多态。重写是子类中定义与父类相同方法签名的方法方法名、参数列表、返回类型一致返回类型可以是父类返回类型的子类发生在继承关系中调用哪个方法在运行期由实际对象类型决定所以叫动态多态。还要补几个细节这些才是能拉开差距的地方。重写方法的访问修饰符不能比父类更严格比如父类是 public子类不能改成 private。抛出的受检异常范围不能比父类更大。重写方法可以加上 Override 注解让编译器帮忙检查是否真的重写了父类方法漏掉 Override 时如果签名写错代码只会编译成一个新的普通方法应用了多态的地方就会静默出现问题。有一次面试我问对方怎么确保自己真的重写了方法有人回答“看方法名一样就对了”这种直接暴露了平时写代码不认真。写业务代码时在实现接口或者继承父类覆盖逻辑的场景里我都习惯顺手加 Override这不是仪式感是编译器给你兜底的检查工具。4.2 第6问接口和抽象类项目里到底怎么选这道题的答案背下来不难难点是怎么结合需求把选择理由说清楚。面试官真正想听的是你有没有设计意识和场景判断力。接口的核心是约定和契约它只告诉使用者能做什么不关心怎么做。一个类可以同时实现多个接口这解决了 Java 单继承的限制。抽象类则是提供了部分实现的模板抽象方法让子类去完成具体逻辑已经实现的方法可以直接复用其中还可以有成员变量、构造方法、非抽象方法。Java 8 之后接口里可以写 default 方法和 static 方法Java 9 又允许 private 方法看起来接口和抽象类之间的边界模糊了但本质没变接口强调能力has-a 或者 like-a 的关系比如一个类可以 Runnable、Serializable、Comparable 全都具备抽象类强调血缘关系is-a 的关系比如所有动物都有呼吸这个动作就可以在抽象动物类中实现呼吸方法。实际开发中我的选择原则是优先定义接口把系统之间的依赖抽象成接口实现类再去实现。抽象类更适合做代码复用的模板典型的就是模板方法模式比如一个业务流程的骨架放在抽象类里把校验、通知等步骤留成抽象方法子类扩展变的部分。一句话能讲清接口管“能不能”抽象类管“是什么”。如果你拿不准就先定义接口这通常不会错。4.3 第7问哪些异常可以不用管哪些必须管异常体系的问题经常出在“背概念很熟落到代码里就不知道哪些该 catch哪些不该 catch”。先梳理 Throwable 分裂出来的两大分支Error 和 Exception。Error 是 JVM 内部严重问题比如 OutOfMemoryError、StackOverflowError这类问题程序通常无法恢复不应该去捕获。Exception 又分两类编译期受检异常和运行时异常。受检异常在编译阶段强制要求捕获或者用 throws 声明比如 IOException、SQLException运行时异常则包括 NullPointerException、IllegalArgumentException、ClassCastException 这些编译器不强制要求处理。面试官容易追问“实际代码里你 catch 什么”。这里很考验功力。我见过不少人在业务代码里 catch Exception 后打个日志就过去了甚至有的 catch 空转等于直接把错误吞掉这是很危险的习惯。业务异常建议自定义一个运行时异常比如 BizException然后在合适的地方统一处理而不是在每个方法里到处 try catch。还有两个小细节想提醒。一个是尽量不要捕获 Throwable 和 Error这会把 JVM 级别的严重问题也吞进来系统稳定性受影响另一个是老生常谈但要反复强调的 finally 里不要 return它会覆盖 try 块里的返回值这个 bug 当年坑过不少人。try-with-resources 会让代码干净很多凡是实现了 AutoCloseable 的资源都值得用它写法也更贴近现代 Java 的规范。5. 集合框架一问到 HashMap 就卡壳的人太多了5.1 第8问ArrayList 的扩容机制和容量预估这一问属于初级面里的深水区看起来在问源码其实在考察你写代码时有没有容量意识。标准回答聚焦扩容过程。无参构造的 ArrayList 在 JDK 8 里初始并不是创建一个容量 10 的数组而是一个空的 Object 数组直到第一次 add 时才把数组扩容成默认容量 10这叫延迟初始化。之后每次添加元素如果容量不够就触发扩容新容量是旧容量的 1.5 倍oldCapacity (oldCapacity 1)。扩容本身是新建一个更大的数组然后把旧数组元素用 System.arraycopy 复制过去这个过程是 O(n) 的频繁扩容性能不好。所以要主动预估容量。如果大致知道要放多少条数据创建时就写 new ArrayList(expectedSize)而不是等它自己一次次扩容。比如从数据库查出一万条数据再拼装到 ArrayList 里预先指定容量可以省掉多次数组复制和 GC。追问预警modCount 是另一个高频点。迭代过程中如果别的地方修改了集合结构modCount 会变化迭代器遍历时发现 modCount 不一致就会抛 ConcurrentModificationException。这不是线程安全设计只是一个快速失败检测机制。如果多线程并发操作同一个 ArrayList正确做法不是去 catch 这个异常而是换线程安全的集合。5.2 第9问HashMap 的 put 方法完整链路HashMap 是所有 Java 面试里最经典的一道题没有之一。回答时不要直接背源码应该按执行链路一层层讲。首先计算哈希。key 的 hashCode 拿到后JVM 会把高 16 位和低 16 位做异或运算得到最终的 hash。这样做的原因是底层数组长度通常不大寻址用的是 (n - 1) hash只有低位参与运算如果直接把 hashCode 拿过来用高位变化不会影响数组下标冲突就会变多。异或混合高低位后可以让低位里混入高位的信息分布更均匀。然后定位下标。数组长度 n 是 2 的幂所以用 (n - 1) hash 等价于 hash % n位运算比取模更快。JDK 8 保证数组长度总是 2 的幂也正因为这里用与运算代替了取模。接着分情况处理。如果该下标没有元素直接放进新节点。如果已经有了元素分三种情况第一个节点的 key 和当前 key 通过 equals 判断相等直接覆盖值否则如果第一个节点是红黑树节点走红黑树插入逻辑否则说明是链表从表头开始遍历找到 equals 相等的就覆盖没找到就在链表尾部插入新节点。插入完成后如果链表长度达到树化阈值 8并且整个数组长度达到 64就会把这块链表转成红黑树。最后判断是否需要扩容。每次插入后 size 加一如果 size 超过 thresholdthreshold 等于容量乘加载因子默认是 16 * 0.75 12就触发扩容容量翻倍到 32元素重新散列。加载因子 0.75 是时间和空间上的一个折中太小浪费空间太大增加碰撞概率。我面试时一般会让候选人把 put 的全过程画在纸上或者口头讲出来重点观察对树化条件的描述是否完整。很多人能说出链表长度到 8但漏了“数组长度至少 64”这个前置条件这就是细节中的细节。5.3 第10问树化阈值和并发安全这一问一般在高级一点的面试里作为 HashMap 的收尾但基础面试也常常问到“为什么是 8”和“为什么并发不安全”。为什么链表长度到 8 才转红黑树因为官方源码注释里提到近似泊松分布在随机哈希下链表长度达到 8 的概率已经极低大约千分之一以下说明此时哈希严重冲突才需要升级为树来保证查询效率。链表查询是 O(n)红黑树是 O(log n)树化的成本是节点本身要变成树节点占用空间更大所以不能一有冲突就树化只在极端场景下才值得。为什么转为红黑树后长度小于 8 时又转回链表因为实际扩容后桶里的元素会被分散到两个新桶里树结构可能退化如果长度降到 6就拆回链表。这里 7 是缓冲值防止元素在 8 附近频繁增删导致树和链表反复切换白白浪费时间。HashMap 并发不安全要讲清楚两个层面一是 put 时扩容多线程同时扩容可能丢数据二是 JDK 7 的头插法在并发扩容时会形成环形链表导致 get 死循环。JDK 8 改成尾插法之后环的问题基本不再出现但数据丢失和覆盖问题依然存在所以并发场景不能直接用 HashMap。并发场景的替代方案是 ConcurrentHashMap。JDK 8 的 ConcurrentHashMap 放弃了早期的分段锁改用 CAS 配合 synchronized锁粒度细化到每个桶扩容时支持多线程协助迁移统计 size 用了类似 LongAdder 的计数方案并发度比之前高很多。面试时能把升级过程说清楚非常加分。6. 追问实录比背答案更重要的三件事6.1 面试官问完这十道题会顺着往哪追问这十题答完之后面试官几乎必然会做一次衍生追问提前准备才不会慌。字符串板块会往 String 池、intern、常量池和堆的关系上追甚至可能问“为什么常量池要设计在堆里”这类 JVM 相关的问题。HashMap 会往红黑树和 ConcurrentHashMap 的扩容细节上追也可能让你比较 HashMap 和 Hashtable。equals/hashCode 这组通常会把话题带向集合类排序、去重的实现比如 TreeSet 为什么用 Comparable 而不用 hashCode。我的建议是每道题准备一个“第二层答案”不需要十道题全部覆盖但至少把 String、HashMap、equals/hashCode 这三个高频追问点的第二层准备到位。这三块足够你撑过基础面的前半程。6.2 现场发挥的注意事项见过太多真实面试场景发现几个共性问题。第一个是只背结论不解释原因。说“HashMap 用红黑树是为了提高效率”没问题但面试官想听的是为什么 8 转、为什么 6 转回、空间代价是什么。主张多答“因为”两个字是用场景解释原因能明显区分背题和理解透彻的人。第二个是问什么答什么不加一句话限定前提。比如问 ArrayList 扩容你直接说初始容量 10 就够了但 JDK 8 其实是无参构造延迟初始化。不加前提的答案容易让面试官觉得你对版本差异不敏感。加点限定条件比如“在 JDK 8 下默认是”听起来更严谨。第三个是别乱扯无关内容。我见过候选人答 HashMap 时开始背 ConcurrentHashMap 的全部源码流程结果关键点反而没讲到。面试是交流不是展示你知道全部知识点顺着面试官的节奏走比抢话重要得多。6.3 我的一些备考心得这十道题我既作为候选人准备过也作为面试官考过别人两边的感受合在一起说最有效的复习方式不是反复看材料而是把每道题当成一个故事讲出来。每一问都要讲出三件套它解决什么问题、它内部怎么实现、它有什么代价或限制。比如 String 不可变解决的是共享安全与缓存问题实现方式是 final 类与 final 字符数组代价是拼接时产生新对象。能串起这三件事面试官问任何变体你都能接住。另外复习时可以用“同一知识块”的方式记忆比如把 / equals / hashCode / HashMap / ConcurrentHashMap 放在一条线里反复串讲这样彼此之间能互相触发记忆现场发挥也更加连贯。最后说一句这期内容之外的体会八股文不是让你死记硬背的洪水猛兽它本质上是在考察一个人有没有把语言、规范、底层机制真正串起来。如果你复习时能做到“每个问题都能用自己的话讲给别人听”面试结果通常不会差。祝这期题目能帮你在面试现场多一分底气。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑