Java 基础与集合面试题精选
Java 后端真实面试专题 · Java 基础与集合篇
基础不牢面试官第一关就刷人。这一篇是真实面经里的开胃高频题,每题三段: ① 标准答(讲透)→ ② 拓展(成体系带出关联点和必追问的)→ ③ 怎么接到你自己的项目。
年限标签:
🟢 3年内🔴 3年+
1. 🟢 面向对象的三大特性?
标准答:
- 封装:把数据和操作封装在类里、对外暴露接口、隐藏实现细节(private + getter/setter),降低耦合、保护数据。
- 继承:子类复用父类的属性和方法、扩展功能,体现"is-a"关系。
- 多态:同一个引用调用同一个方法,运行时根据实际对象表现不同行为(父类引用指向子类对象)。
拓展:
- "多态怎么实现的?"——靠动态绑定:编译看引用类型、运行看实际对象类型。
- 继承的缺点:耦合强,所以"组合优于继承"。
- 多态是策略模式、工厂模式的基础。
往项目引 ⭐:"我项目多渠道支付就是多态的实战——定义支付接口,微信、支付宝各实现,用父类型引用调用、运行时执行实际实现。三大特性不是背概念,封装/继承/多态在我项目代码里到处是。"
2. 🟢 重载(Overload)和重写(Override)的区别?
标准答:
- 重载:同一个类里,方法名相同、参数列表不同(个数/类型/顺序),和返回值无关。编译期确定(静态)。
- 重写:子类重写父类的方法,方法签名相同,运行期确定(动态多态)。要求访问权限不小于父类、异常不大于父类。
拓展:
- "重写的限制?"——
@Override校验、不能重写 final/static/private 方法、返回值可协变。 - 重载是编译期多态、重写是运行期多态。
往项目引 ⭐:"我项目里 Service 实现接口方法是重写、工具类提供多个参数版本的方法是重载——能各举一个例子,比背定义有说服力。"
3. 🟢 == 和 equals 的区别?为什么重写 equals 要重写 hashCode?
标准答:
==:基本类型比值、引用类型比地址。equals:默认也是比地址(Object 的实现),但常被重写成比内容(如 String、自定义对象)。- 重写 equals 必须重写 hashCode:因为 HashMap 等依赖"equals 相等的对象 hashCode 必须相等",否则同一个对象可能定位到不同桶、出现找不到的 bug。
拓展:
- "hashCode 相等,equals 一定相等吗?"——不一定(哈希冲突);但 equals 相等 hashCode 必须相等。
- 这题常和 HashMap 原理连着问。
往项目引 ⭐:"我项目里自定义对象做去重/做 Map 的 key 时,一定同时重写 equals 和 hashCode——踩过坑:只重写 equals 没重写 hashCode,导致 Set 去重失效、对象重复。"
4. 🟢 Integer 的缓存机制?Integer a=100,b=100; a==b 是 true 吗?127 呢?128 呢?
标准答:Integer 缓存了 -128~127 的对象。Integer a=100,b=100(在缓存范围内)a==b 是 true(同一个缓存对象);Integer a=200,b=200(超出范围)a==b 是 false(各自 new 的对象)。所以比较 Integer 值要用 equals。
拓展:
- 这是自动装箱触发的——
Integer a=100等于Integer.valueOf(100),valueOf 在缓存范围内返回缓存对象。 - "为什么缓存这个范围?"——小整数用得最多,缓存省内存。
- 金额、id 比较用 Integer 一律 equals,别用 ==。
往项目引 ⭐:"我项目里订单状态、数量这些用 Integer 比较一律用 equals——曾经有人用 == 比 Integer,数值大于 127 时偶发判断错误,排查半天才发现是装箱缓存的坑。"
5. 🟢 String、StringBuilder、StringBuffer 的区别?
标准答:
- String:不可变,每次拼接产生新对象。
- StringBuilder:可变、非线程安全、最快,单线程拼接首选。
- StringBuffer:可变、方法加了 synchronized、线程安全但慢。
拓展:
- "String 为什么不可变?"——内部 char/byte 数组 final,好处是安全(做 key/参数)、可缓存 hashCode、支持常量池复用。
- "
String a = "a"+"b"会创建几个对象?"——编译期常量折叠成 "ab",一个。 - 循环拼接用 StringBuilder,别用
+(每次生成新 String + StringBuilder)。
往项目引 ⭐:"我项目里拼 SQL、拼日志、拼大字符串一律用 StringBuilder——循环里用 + 会产生大量临时对象加重 GC。并发拼接才用 StringBuffer,但那种场景很少。"
6. 🟢 接口和抽象类的区别?什么时候用哪个?
标准答:
- 抽象类:可以有构造器、成员变量、具体方法,单继承,体现"is-a",用于有共同实现的一类对象。
- 接口:定义行为契约,多实现,JDK8 后能有默认方法/静态方法,体现"can-do",用于定义能力。
拓展:
- "JDK8 接口能有方法体了,还要抽象类吗?"——要,抽象类能有状态(成员变量)和构造器、做模板方法更合适。
- 设计原则:优先面向接口编程。
往项目引 ⭐:"我项目里用接口定义支付能力(PayService)让各渠道实现,用抽象类做流程模板(公共步骤写在抽象类、差异步骤留抽象方法给子类)——按'定义能力用接口、复用实现用抽象类'来选。"
7. 🟢 Java 异常体系?检查异常和非检查异常的区别?
标准答:顶层是 Throwable,分 Error(系统级错误,如 OOM,不该 catch)和 Exception。Exception 又分:
- 检查异常(受检):编译期强制处理(try-catch 或 throws),如 IOException、SQLException。
- 非检查异常(运行时异常 RuntimeException):编译期不强制,如 NPE、数组越界、ClassCastException。
拓展:
- "Spring 事务默认对哪种回滚?"——只对 RuntimeException 和 Error 回滚,受检异常要配 rollbackFor。
- 自定义业务异常一般继承 RuntimeException(不想强制处理)。
- finally 一定执行(除非 JVM 退出)。
往项目引 ⭐:"我项目自定义业务异常 BusinessException 继承 RuntimeException,配合全局异常处理器统一返回错误码。知道'Spring 默认只回滚 RuntimeException',所以业务异常都是运行时异常,事务才会正确回滚。"
8. 🟢 反射是什么?有什么优缺点?能调用私有方法吗?
标准答:反射是运行期动态获取类的信息(构造器、方法、字段)并操作的能力。优点:灵活、解耦,框架的基石(Spring 创建 Bean、MyBatis 映射)。缺点:性能比直接调用差、破坏封装、编译期检查不到。能调私有方法——setAccessible(true) 后可调用私有方法/字段。
拓展:
- "反射为什么慢?"——要做安全检查、不能被 JIT 充分优化(可缓存 Method 对象缓解)。
- 框架几乎都靠反射 + 注解工作。
往项目引 ⭐:"我项目里写过一个通用工具——用反射把 Excel 的列自动映射到实体字段,靠反射动态 set 值。理解反射也让我看懂 Spring/MyBatis 这些框架'自动注入、自动映射'的底层。"
9. 🟢 Java 集合框架的整体结构?
标准答:两大体系——
- Collection:List(有序可重复:ArrayList、LinkedList)、Set(去重:HashSet、TreeSet、LinkedHashSet)、Queue(队列:LinkedList、PriorityQueue、ArrayDeque)。
- Map(键值对):HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap、Hashtable。
拓展:
- TreeMap/TreeSet 有序(红黑树)、LinkedHashMap 保留插入/访问顺序(可做 LRU)。
- 并发用 ConcurrentHashMap、CopyOnWriteArrayList,别用 Vector/Hashtable(锁整个,性能差)。
往项目引 ⭐:"我项目按场景选集合——去重用 HashSet、要排序用 TreeMap、做 LRU 缓存用 LinkedHashMap、并发计数用 ConcurrentHashMap。能说出'什么场景用哪个、为什么'就够。"
10. 🟢 ArrayList 和 LinkedList 的区别?
标准答:
- ArrayList:底层数组,随机访问 O(1),中间增删要移动元素 O(n),省内存。
- LinkedList:底层双向链表,头尾增删 O(1),随机访问要遍历 O(n),每个节点有额外指针开销。 实际开发 90% 用 ArrayList(随机访问多、缓存友好)。
拓展:
- "ArrayList 扩容?"——默认 10,扩容 1.5 倍,
Arrays.copyOf复制。 - "LinkedList 真的增删快吗?"——只有"已定位到位置"时快,按索引增删还要先遍历找位置。
往项目引 ⭐:"我项目里列表数据基本都用 ArrayList——随机访问多、CPU 缓存友好。LinkedList 几乎没用,因为它的'增删快'在大多数场景体现不出来还更占内存。"
11. 🟢 HashMap 的底层原理?put 流程?
标准答:JDK8 是 数组 + 链表 + 红黑树。put 流程:对 key 的 hashCode 扰动(高低位异或)后 & (n-1) 定位桶 → 桶空直接放 → 桶非空且 key 相同则覆盖 → 否则挂链表 → 链表长度 ≥8 且数组 ≥64 转红黑树 → 检查是否超阈值扩容。
拓展:
- "为什么链表长度 8 转红黑树?"——泊松分布下冲突到 8 的概率极低,转树是兜底优化查询。
- "红黑树什么时候退化链表?"——节点 ≤6 时。
- "为什么扰动?"——让高位也参与运算、减少冲突。
往项目引 ⭐:"HashMap 是面试'铁三角'之一,几乎必问。我项目里大量用 HashMap 做缓存和映射,理解它的扩容和冲突机制让我能合理设初始容量、避免频繁扩容。"
12. 🟢 HashMap 为什么扩容是 2 倍?初始容量多少?
标准答:初始容量 16、负载因子 0.75(元素超过 容量×0.75 就扩容)。扩容为 2 倍,因为容量是 2 的幂时,hash & (n-1) 等价于取模、且效率高(位运算);2 倍扩容时,元素要么在原位置、要么在"原位置+原容量",rehash 高效。
拓展:
- "负载因子为什么 0.75?"——空间和时间的平衡,太大冲突多、太小浪费空间。
- "为什么用位运算定位桶?"——比取模快。
- 知道大概元素数量时,设合理初始容量避免多次扩容。
往项目引 ⭐:"我项目里如果知道 Map 大概要放多少元素,会设初始容量(如预计 100 个就设 128),避免默认 16 反复扩容、rehash 影响性能。"
13. 🟢 HashMap 为什么线程不安全?并发场景用什么?
标准答:并发 put 时——JDK7 头插法扩容可能形成环形链表导致 CPU 100%;JDK8 改尾插避免了环,但并发 put 仍可能丢数据、覆盖。并发用 ConcurrentHashMap:JDK8 用 CAS(桶空)+ synchronized(锁单个桶头节点),只锁单桶、并发度高。
拓展:
- "为什么不用 Hashtable?"——它锁整个表,性能差。
- "ConcurrentHashMap 的 size 怎么算?"——baseCount + CounterCell 分散统计。
- "key/value 能为 null 吗?"——ConcurrentHashMap 不能(并发下歧义)。
往项目引 ⭐:"我项目并发统计、本地缓存都用 ConcurrentHashMap,不用 Collections.synchronizedMap(锁整个)。比如各接口调用量统计用 ConcurrentHashMap<String, LongAdder>,高并发下又准又快。"
14. 🟢 Java 是值传递还是引用传递?
标准答:Java 只有值传递。基本类型传值的副本;对象传引用的副本(地址值的拷贝)——方法里能通过引用改对象的内容,但重新赋值引用不影响外部。
拓展:
- 经典例子:方法里给传入的对象 set 属性,外面能看到改变(改的是同一个对象);方法里把参数指向新对象,外面不受影响(改的是引用副本)。
- 很多人误以为对象是引用传递,其实是"传递引用的值"。
往项目引 ⭐:"理解'传引用的副本'帮我避免过 bug——以为在方法里把参数重新赋值能影响外部,其实不能。改对象内容可以、改引用指向不行。"
15. 🟢 BigDecimal 是什么?为什么金额要用它?
标准答:BigDecimal 是高精度十进制数。金额绝不能用 float/double——浮点数二进制表示有精度丢失(0.1+0.2 != 0.3)。BigDecimal 精确计算。注意:要用字符串构造(new BigDecimal("0.1")),别用 double 构造(仍有精度问题);比较用 compareTo 不用 equals。
拓展:
- "为什么 double 不精确?"——很多十进制小数没法用有限二进制精确表示。
- 除法要指定精度和舍入模式,否则除不尽会异常。
往项目引 ⭐:"我项目所有金额计算用 BigDecimal、字符串构造、compareTo 比较、除法指定舍入。这是涉及钱的铁律,用 double 算钱迟早出精度事故。"
16. 🟢 fail-fast 和 fail-safe 是什么?
标准答:
- fail-fast(快速失败):遍历集合时如果被修改(结构变化),立刻抛
ConcurrentModificationException。如 ArrayList、HashMap 的迭代器。靠 modCount 检测。 - fail-safe(安全失败):遍历的是副本,修改不影响遍历、不抛异常。如 CopyOnWriteArrayList、ConcurrentHashMap。
拓展:
- "怎么在遍历时安全删除?"——用迭代器的
remove(),别用集合的 remove。 - 并发修改集合用并发容器。
往项目引 ⭐:"我项目里踩过 ConcurrentModificationException——在 for-each 里删元素。改用迭代器的 remove 或并发容器解决。理解 fail-fast 的 modCount 机制才知道为什么。"
17. 🟢 final 关键字的作用?
标准答:
- 修饰变量:基本类型值不可变、引用类型引用不可变(但对象内容可变)。
- 修饰方法:不能被重写。
- 修饰类:不能被继承(如 String)。
拓展:
- final 变量保证可见性(JMM 对 final 有特殊保证)。
- 局部 final 变量才能被匿名内部类/Lambda 捕获。
- final 不等于不可变(对象内容还能改)。
往项目引 ⭐:"我项目常量用 static final、不希望被改的引用用 final、工具类用 final 防继承。Lambda 里引用的外部局部变量也必须 final(事实 final)。"
18. 🟢 深拷贝和浅拷贝的区别?
标准答:
- 浅拷贝:只复制对象本身和基本类型字段,引用类型字段还指向同一个对象(共享)。
- 深拷贝:连引用的对象也递归复制一份,完全独立。
拓展:
- 实现深拷贝:递归 clone、序列化反序列化、用工具(如 JSON 转换)。
- Object.clone() 默认是浅拷贝。
往项目引 ⭐:"我项目里需要'改副本不影响原对象'时用深拷贝(常用 JSON 序列化反序列化实现)——比如把一个配置对象拷一份给某次请求改,不能影响共享的原配置。"
19. 🟢 JDK8 有哪些新特性?
标准答:Lambda 表达式、Stream 流式 API、函数式接口、Optional(优雅处理 null)、新日期时间 API(LocalDateTime)、接口默认方法、元空间替代永久代。
拓展:
- Stream 的 map/filter/collect/groupingBy 大幅简化集合操作。
- Optional 避免 NPE。
- 这题常顺着问 Stream 用法、Lambda 本质(函数式接口实现)。
往项目引 ⭐:"我项目大量用 Stream 处理集合——分组统计用 groupingBy、转换用 map、过滤用 filter,代码比传统 for 循环简洁很多。也用 Optional 优雅处理可能为空的返回值。"
20. 🟢 Java 里的泛型是什么?类型擦除了解吗?
标准答:泛型提供编译期类型检查、避免强转。Java 泛型是伪泛型——编译后会类型擦除,泛型信息被擦除成原始类型(如 List<String> 运行时就是 List),靠编译期检查保证安全。
拓展:
- "类型擦除带来什么限制?"——不能
new T()、不能用基本类型(要用包装类)、运行时拿不到泛型类型(要靠传 Class 或 TypeReference)。 - 通配符:
? extends(上界,读)、? super(下界,写)。
往项目引 ⭐:"我项目封装统一返回 Result<T> 用了泛型保证类型安全;反序列化泛型集合时因为类型擦除,要用 TypeReference 传递泛型信息——踩过'泛型运行时拿不到类型'的坑才理解类型擦除。"
你能答到第几层?
- 三段都能答、还能往项目引:基础这块你过关稳稳的。
- 标准答 + 拓展能成体系答:基础扎实,差把它接到项目里的实际用法。
- 标准答都磕巴:基础是地基(OOP → 集合 → 异常 → 泛型),系统过一遍就稳,面试第一关靠它。
这是面试专题的「Java 基础与集合篇」,网站上还有并发、MySQL、Redis、Spring、微服务、消息队列、JVM、安全认证、项目场景等系统整理。 🌐 更多真实面试专题与资料:smallredtech.com 💬 想系统学 / 简历与辅导咨询,加微信:Ahongbb666(备注「面试题」)