ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java基础面试反问:String、HashMap与异常原理,122道题的体系化拆解

Java基础面试反问:String、HashMap与异常原理,122道题的体系化拆解 上个月帮团队做Java开发岗位的面试连着面了十几个候选人简历上清一色写着“扎实的Java基础”“熟练掌握Java核心机制”。但当我把准备好的基础题拿出来问到String创建了几个对象、HashMap自动扩容的过程、受检异常和非受检异常的分界线不少人当场卡壳。不是完全不会而是答得太浅要么只丢结论要么在用背下来的话硬撑。网上流传的“java面试题及答案基础题122道”我也看过质量参差不齐有些答案本身就有问题。但作为一个面试过别人、也被面试过的人我想说刷题本身没错错的是把刷题当成背答案。这套题真正值钱的不是那122个标准答案而是它背后覆盖的知识体系。这篇文章我想从这122道题里把面试官最爱问、候选人最易翻车、并且藏着完整原理的典型题目挑出来拆开揉碎讲一遍。1. 基础题重不重要先看面试官为什么从这入手很多候选人会抱怨我简历上写的都是分布式、消息队列、Redis为什么面试官一上来问我ArrayList和LinkedList的区别这也太基础了吧恰恰相反。基础题是面试官用来快速判断“这个人平时写代码到底有没有动脑”的工具。一个能把HashMap底层扩容细节讲清楚的人你让他去聊分布式缓存的一致性一般也差不到哪里去因为他的知识是连成体系的一个只会背“线程不安全”结论的人换个场景立刻就不灵了。所以基础题不是走过场它是面试里投入产出比最高的一类题目。1.1 从122道题提炼出的知识点地图这套基础题看着多但拆开之后核心板块其实就那么几块知识板块在基础题中的出现频率典型题目方向面向对象与语法很高三大特性、重载重写、接口抽象类String与包装类很高String创建对象、与equals、自动拆装箱集合框架很高HashMap原理、集合区别、fail-fast异常与IO中高受检异常、try-with-resources、流分类多线程与JVM基础中sleep与wait、synchronized、JDK/JRE/JVM反射与其他中低反射机制、final/finally/finalize我建议刷题之前先把这张表刻在脑子里。拿到一道题第一反应不是“答案是什么”而是“它属于哪个板块和哪些知识点相关”。有了这个分类能力122道题就能压缩成六大块去理解和记忆而不是100多个孤立的问答。1.2 把122道题变成体系的刷法我自己刷这类基础题用过一套方法效果比死背好很多。第一遍快刷把题目按上面的板块分类搞懂每道题在问什么。第二遍进入精刷把做错的、卡壳的题按板块归拢找出自己系统性的薄弱点而不是零散地补。比如你ArrayList和HashMap的题都错那问题出在“集合框架”这个板块需要整体重新过一遍底层结构而不是只背某一道题。第三遍是输出关上题单把每个板块的核心知识点自己讲给自己听讲不通的地方就是没理解透的地方。这套流程走完你手里拿到的不是122个答案而是一张密实的Java基础知识网。2. 面向对象最基础也最能拉开始终差距面向对象在Java基础题里占比最高而且面试官问法极其灵活。今天问“封装继承多态是什么”明天换个场景让你说说某个设计为什么要这么写。如果能从原理层面答透后面聊框架、聊设计模式都会顺畅很多。2.1 “谈谈你对面向对象的理解”怎么答才不空很多人一开口就是“封装就是把属性私有化继承就是子类继承父类多态就是一个对象多种形态”三句话说完就停了。这不算错但太干。要答出深度需要往下一层拆封装是通过访问修饰符控制成员的可访问范围核心目的是隐藏实现细节、降低耦合。Java里public、protected、private以及包访问权限就是在语法层面支撑封装的工具。举个例子一个类内部维护着订单状态流转逻辑外部只需要调用下单接口不需要知道状态是怎么校验的这就是封装的价值。继承是复用和扩展父类能力的手段。Java用extends关键字实现继承注意Java是单继承但可以配合接口实现多类型约束。多态需要在继承和重写的基础上配合父类引用指向子类对象来实现。它的价值在于面向抽象编程。比如你有一个Animal类型的变量具体指向的是Dog还是Cat调用同一个方法会有不同的行为调用方不需要关心具体子类型。如果面试官让你再深挖可以补充多态的三个必要条件有继承关系、子类重写了父类方法、父类引用指向子类对象。这三句话一说答案的完整度就上来了。2.2 重载和重写的区别别只背结论这是122道题里几乎必出的一道。常见的答法是“重载是方法名相同参数不同重写是子类重新实现父类方法”。但真正拉开差距的是对编译期和运行期的理解。看一段简单的代码class Parent { public void say(String msg) { System.out.println(Parent: msg); } } class Child extends Parent { Override public void say(String msg) { System.out.println(Child: msg); } public void say(int times) { System.out.println(Child int: times); } }重载是编译期的多态同一个类里方法名相同参数类型、个数、顺序不同返回类型不参与比较。调用哪个重载方法编译器在编译阶段就决定了。重写是运行期的多态子类和父类方法签名一致JVM在运行期根据实际对象类型决定调用哪个方法。面试官在这个问题上还藏了一个隐藏分重写有哪些规则。答案很重要子类方法的访问权限不能比父类更低抛出的异常范围不能比父类更宽方法签名必须一致。很多人背了重写定义但说不全这两条规则就是这个题目的分水岭。2.3 接口和抽象类怎么选才符合真实场景这也是基础题里的常客。直接给一张对比表对比维度抽象类接口关键字abstract classinterface继承方式单继承多实现成员变量可以有实例字段默认常量构造方法可以有不能有方法实现抽象方法普通方法Java 8后可default方法Java 9后可private方法语义“是什么”的抽象“能做什么”的能力约定这里特别提醒一个时点问题。Java 8之前接口里只能有抽象方法所以很多人背的是“接口只能定义抽象方法”。但Java 8之后接口支持默认方法和静态方法Java 9之后还支持私有方法这个知识点已经更新了。如果你在面试里说“接口只能有抽象方法”面试官大概率会追问Java 8带来的变化。这不是坑是在考察你有没有跟着语言版本更新知识体系。实际选择上我的习惯是如果一群类本质上属于同一类事物比如猫和狗都属于动物用抽象类如果要定义一种跨类别的能力比如“可以飞行”“可以被序列化”用接口。Java的单继承限制决定了接口在系统设计中扮演的角色通常比抽象类更灵活。3. String、包装类与final基础题里最会挖坑的三兄弟如果你翻过那122道基础题会发现几乎有三分之一的高频题都绕着String、包装类和final打转。这三个知识点单独看都不难但组合起来就是送命题制造机。3.1 String到底创建了几个对象这个题被问烂了但至今还是有很多人说不对。看这段代码String s1 java; String s2 new String(java); System.out.println(s1 s2); // false第一行如果字符串常量池里还没有“java”JVM会在常量池创建这个字面量。第二行代码在堆上new了一个新的String对象同时这个对象的内部value数组会复用常量池里的“java”字符串。所以s1指向常量池对象s2指向堆对象两个引用地址不同比较结果为false。回答这道题关键是把“常量池里创建一个对象”和“堆里new一个对象”分开说。然后可以主动补充String设计成不可变有三个核心好处。一是安全String对象经常被当作参数传递不可变就不会被恶意修改二是支持常量池复用相同内容的字符串可以共享节省内存三是hashCode只需计算一次作为HashMap的key性能更好。3.2 和equals从比较陷阱到Integer缓存出题人最爱把和equals以及包装类缓存放在一起问。这段代码是经典中的经典Integer a 127; Integer b 127; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false比较的是引用地址。a和b之所以相等是因为Integer内部有缓存机制默认缓存-128到127区间的对象赋值在这个范围内时直接复用缓存对象。超过127就会各自new一个Integer对象地址不同为false。equals则不同String、Integer这些包装类型都重写了equals方法比较的是内容而不是地址。所以比较两个对象的值时永远用equals而不是。面试官往往还会追问一句那为什么默认情况下的equals也是因为Object类里的equals方法本身用的就是只有子类重写后才会变成内容比较。能顺着这条线答下来这道题基本就是满分。3.3 自动拆装箱与隐藏的空指针自动拆装箱是Java 5加入的语法糖Integer和int可以自动转换。语法很香但坑很隐蔽。最经典的例子Integer count null; int n count; // 这里会抛NullPointerException自动拆箱的本质是调用integerValue()方法count是null一调用就空指针。这个题在基础题里不算高难度但它考察的是候选人写代码时对空值的敏感度。回答时最好顺带说出基本类型和包装类型的区别包装类型是对象可以是null存在于堆上基本类型不是对象没有null的概念存在栈上或作为对象字段写在堆里。3.4 final、finally、finalize一道题拆三个概念“final、finally、finalize的区别”在122道题里基本是固定节目。final是修饰符修饰类表示类不可继承修饰方法表示方法不可重写修饰变量表示变量一旦赋值就不能再改。finally是异常处理的关键字用于try-catch之后的收尾无论是否发生异常都会执行。finalize是Object类里的一个方法曾经用于对象被回收前的清理钩子现在已经标记为废弃不应该依赖它做任何资源释放。这道题表面考记忆实际考对异常处理流程和对象生命周期的理解。能顺带提一句“JDK 9里finalize已经被标记为弃用更推荐用Cleaner或者try-with-resources管理资源”就会给面试官留下“这个人有跟进版本”的印象。4. 集合框架基础题里的重灾区八成翻车在这集合框架是Java基础面试题里的绝对主战场。原因很简单日常开发、框架源码、以及JDK自身API里到处都是集合的身影面试官随便挑一个点都能往深了聊。4.1 ArrayList和LinkedList只答“一个数组一个链表”不够这是122道题里被抽中概率极高的一道。很多人第一反应就是“ArrayList底层是数组LinkedList底层是链表”说完就停了。但面试官更想知道的是时间复杂度。ArrayList基于数组get操作是O(1)因为可以直接通过下标定位。add操作如果加在末尾均摊也是O(1)但如果在中间插入需要把后面的元素整体移动最坏O(n)。LinkedList基于双向链表get操作需要从头部或尾部遍历才能拿到对应下标的节点是O(n)这一点很多人会答错——以为链表定位也快。插入删除操作如果在头部或者尾部确实是O(1)但如果你要在第index个位置插入还是要先遍历找到那个位置所以实际也是O(n)。另外ArrayList还有一个高频考点扩容机制。默认初始容量是10当元素数量超过容量时会触发扩容每次扩容到原来的1.5倍具体扩多少可以记一下源码里的表达式oldCapacity (oldCapacity 1)。扩容的过程是申请新数组、把旧元素逐个拷贝过去这是有性能开销的所以如果提前知道数据量建议在构造ArrayList时就指定初始容量。4.2 HashMap的底层原理与红黑树阈值HashMap是集合框架里最核心的题。回答这道题建议按版本线讲。JDK 7及以前HashMap底层是数组加链表。JDK 8开始底层改成了数组加链表加红黑树。当链表长度超过8且数组长度大于等于64时链表会转成红黑树目的是把极端情况下的查询时间复杂度从O(n)降到O(log n)。默认初始容量是16加载因子是0.75当元素数量超过capacity乘以0.75这个阈值时会触发扩容扩容后容量翻倍。这里有两个容易答不到位的细节。第一个为什么转红黑树的阈值选8这个数字不是随便拍的它来自泊松分布。在随机哈希码并且负载因子是0.75的情况下链表长度达到8的概率非常低所以8是工程上一个兼顾性能和空间的合理取值。第二个HashMap为什么线程不安全因为并发环境下多个线程同时put时可能触发数据覆盖JDK 7里并发扩容还可能产生循环链表导致get时死循环JDK 8改成尾插法后解决了一部分问题但线程安全问题依然存在。并发场景请用ConcurrentHashMap。4.3 HashSet、迭代器与fail-fast机制HashSet在基础题里出现频率也很高。它的底层其实就是包装了一个HashMapadd元素时把元素作为HashMap的keyvalue用一个固定的常量对象。因为HashMap的key不允许重复所以HashSet天然具备去重能力。fail-fast机制是集合框架里的另一个经典知识点。当你用迭代器遍历集合时如果中途其他线程修改了集合结构也就是增删元素迭代器会发现内部的modCount和预期值不一致然后抛出ConcurrentModificationException。这个机制是快速失败的意思是不保证最终一致而是发现不一致马上报错。很多面试官会接着问那遍历时想删元素怎么办正确做法不是list.remove()而是用迭代器自己的remove方法。原因就在于迭代器的remove方法会同步修改modCount保证迭代安全。或者并发场景直接使用CopyOnWriteArrayList这类线程安全容器它基于写时复制迭代时不会抛出并发修改异常。5. 异常与IO基础题里最容易被低估的部分异常和IO在日常开发里天天见但面试时很多人答不好。原因通常是平时只想着“try-catch能把错误吞掉”没有认真梳理过异常体系和流的设计逻辑。5.1 受检异常和非受检异常分界线在哪里异常体系的基础题一般这么问受检异常和非受检异常有什么区别各举几个例子。受检异常也叫编译期异常是除了RuntimeException及其子类以外的其他Throwable子类。比如IOException、SQLException、ClassNotFoundException。这类异常编译器强制要求处理要么catch要么在方法签名上throws不处理代码过不了编译。理由也很充分这类异常通常由外部环境引起比如文件不存在、网络中断、数据库连接失败程序必须提前想好应对策略。非受检异常也叫运行时异常是RuntimeException的子类包括NullPointerException、IllegalArgumentException、ArrayIndexOutOfBoundsException等。编译器不强制处理因为这类异常往往代表程序逻辑本身的bug应该通过修复代码来避免而不是靠catch兜底。面试时如果能补充一句“受检异常处理的是外部可预期的问题非受检异常暴露的是代码缺陷”整个回答的层次就完全不同了。5.2 异常处理里反着来的做法基本都会踩坑基础题里经常出现“try-catch-finally和try-with-resources的区别”。try-catch-finally是传统写法finally块的目的是保证资源被释放比如关闭文件流、数据库连接。但写的时候有几个反例很常见。第一不要在finally里return这会覆盖try或catch里的return结果让异常被吞掉。第二不要在catch里只打日志不处理最典型的就是catch(Exception e){}括号里什么都不做问题被静默吞掉排查起来非常痛苦。第三传统写法里资源关闭逻辑要写在finally里代码一多就容易漏。Java 7之后推荐用try-with-resources只要资源实现了AutoCloseable接口就可以在try括号里声明资源代码块结束后JVM会自动调用close方法简洁且能保证关闭。同时它还支持在关闭资源时叠加抑制异常比在finally里手动关要严谨得多。5.3 IO流的分类和一道高配题BIO与NIOIO流基础题喜欢问“Java中有哪几类流”。标准答法是按数据流向分为输入流和输出流按处理单位分为字节流和字符流。字节流的基类是InputStream和OutputStream字符流的基类是Reader和Writer。字节流适合处理二进制数据字符流适合处理文本数据字符流底层本质上也是字节流加编码转换。字节流字符流基类方向InputStreamReader读入OutputStreamWriter写出BIO和NIO的区别是进阶版本的基础题。BIO是阻塞式IO一个线程处理一个连接阻塞期间线程什么都干不了连接多了就会线程膨胀。NIO是非阻塞IO核心组件是Channel、Buffer、Selector。Selector允许一个线程同时管理多个Channel事件来了才处理没有事件就去处理其他事所以能在更少的线程下支撑更多连接。基础题能答到这个程度已经明显超过平均水平。6. 剩下几道高频基础题再加一套能提分的答题思路122道题里还有一些画面感很强的基础题单独拿出来都值得说说。最后再分享一套我个人总结的回答策略。6.1 sleep和wait、synchronized锁基础题里的多线程担当sleep和wait的区别在各家面试题里出现频率极高。sleep是Thread的静态方法作用是让当前线程睡眠指定毫秒数但不会释放已经持有的锁。wait是Object的方法调用后当前线程会释放锁并进入等待状态直到被notify或notifyAll唤醒或者等待超时。这两者的本质区别是释放不释放锁。面试里经常用这个作为判断候选人是否真正理解多线程协作的标志。再往深一步一定要说wait必须在同步代码块或者同步方法中使用否则会抛IllegalMonitorStateException因为wait要操作对象的监视器而监视器的获取依赖synchronized。synchronized本身也是高频题。它有三种用法修饰实例方法锁的是当前实例对象修饰静态方法锁的是Class对象修饰代码块锁的是指定对象。回答synchronized时如果能顺带提一句“可重入性”也就是同一个线程可以重复获得同一把锁讲解锁的原理场景时会更受用。6.2 JDK、JRE、JVM的区别送分题里的隐藏扣分点这是一道很基础的题但也有很多人答不完整。JVM是Java虚拟机负责把字节码翻译成机器指令并执行。JRE是Java运行时环境包含JVM和Java核心类库运行一个Java程序只需要JRE。JDK是Java开发工具包包含JRE和编译、调试等开发工具比如javac、jdb。易错点在于“JDK包含JRE”这层关系。很多人会把三者描述成并列关系其实它们是包含关系JDK在完整版上位于最上层往下是JRE再往下是JVM。回答时建议把层次关系说清楚再顺带说明编译过程和class文件的作用这样这道送分题就变成了印象分加分题。6.3 反射机制一道能把框架和语言机制串起来的题反射在基础题里比较靠后但会问的面试官通常都问得比较深。标准的问法“什么是反射在实际应用中有哪些体现”反射的本质是在程序运行期间通过Class对象获取类的完整信息包括字段、方法、构造函数并且可以动态创建实例、调用方法、修改字段访问权限。核心API包括Class.forName()、getMethod()、getField()等。实际应用里Spring的依赖注入要创建并管理BeanMyBatis的Mapper动态代理要生成接口实现很多ORM框架都要根据实体类映射数据库字段这些底层机制全是反射。所以这个题目表面在问反射实际在考察候选人有没有真正理解自己天天用的框架。6.4 答题策略结论先行原理跟进不懂别装懂回到开头那个问题为什么有人能把答案背得滚瓜烂熟面试还是会翻车我的体会有三条。第一结论先行原理跟进。面试官问“HashMap线程安全吗”你第一时间说“不安全”这是结论再说“并发put会导致数据覆盖JDK 7并发扩容还可能形成循环链表所以要用ConcurrentHashMap”这是原理。有结论再补充原理面试官就能判断你的知识体系是完整的。第二用关键词引导追问。回答时主动说出“常量池”“加载因子”“快速失败”这些关键词表现出你熟悉这个领域的知识边界。面试官顺着追问时只要你是真的理解就能一层层往下答。第三遇到不会的题千万别硬编。坦率说“这个知识点我接触得比较少但据我理解它和XXX有关联我尝试从XXX角度说一下”这种态度反而能保住基础分。面试官也是开发者知道谁都有盲区但不诚实是原则问题。我自己刚入行时也是背题一族把122道题背得滚瓜烂熟结果第一次面试就被“String创建了几个对象”问住因为我背了答案却不理解对象到底创建在哪里常量池又是什么。后来我把刷题方式改成画体系图、写Demo验证、给自己讲题整个人对Java的感觉一下子不一样了。基础题不是终点它是把知识织成网的起点。希望这篇拆解能帮你把那122道题里的水分挤掉留下真正能带走的东西。
返回列表