Java 面试题整合版
Java 基础、集合、并发、JVM、Spring 等高频面试题整理。
Java 面试题(整合版)
整合自 [[Java笔记]] 相关主题:[[设计模式]] | [[网络基础]]
一、Java 基础
1. == 和 equals() 区别
==:
- 基本类型 → 比较值
- 引用类型 → 比较地址
equals():
- 默认(Object)→ 比较地址
- String、Integer 等重写过 → 比较内容
2. 为什么重写 equals() 必须重写 hashCode()
HashMap/HashSet 的工作原理:
1. 先 hashCode() 定位桶
2. 再 equals() 判断是否相同
如果 equals() 相同但 hashCode() 不同 → 同一个对象会放进不同桶 → 出现重复
3. String、StringBuilder、StringBuffer
| 可变 | 线程安全 | 性能 | |
|---|---|---|---|
| String | ❌ 不可变 | ✅ | 拼接慢(创建新对象) |
| StringBuilder | ✅ | ❌ | 高 |
| StringBuffer | ✅ | ✅ | 中 |
4. final / finally / finalize
final:修饰类(不可继承)、方法(不可重写)、变量(不可变)finally:try 后的代码块,通常一定执行finalize:Object 方法,GC 前调用(已废弃,别用)
5. 重载 vs 重写
重载:同类、同名、参数不同 → 编译时多态
重写:父子类、签名相同 → 运行时多态
6. 三大特性
- 封装:隐藏内部实现,暴露接口
- 继承:复用父类能力(is-a)
- 多态:同一个方法在不同对象上表现不同(父类引用指向子类对象)
7. 抽象类 vs 接口
| 抽象类 | 接口 | |
|---|---|---|
| 构造方法 | ✅ | ❌ |
| 成员变量 | 可以有 | 常量(static final) |
| 方法实现 | 可以有普通方法 | JDK8+ 可以有 default/static |
| 单继承 | 一个类只能继承一个 | 一个类可实现多个 |
8. 异常体系
Throwable
├── Error(系统级,OOM、StackOverflow,不用处理)
└── Exception
├── Checked(编译时异常,必须 try-catch,如 IOException)
└── Unchecked(运行时异常,不用显式处理,如 NPE)
9. ArrayList vs LinkedList
| ArrayList | LinkedList | |
|---|---|---|
| 底层 | 动态数组 | 双向链表 |
| 随机查 | O(1) ✅ 快 | O(n) ❌ 慢 |
| 尾部插 | O(1) | O(1) |
| 中间插 | O(n) 搬数据 | O(1) 但需先查位置 |
| 内存 | 紧凑 | 节点额外开销 |
10. 手写字符串反转(双指针)
public static String reverse(String str) {
if (str == null || str.length() <= 1) return str;
char[] chars = str.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
char tmp = chars[left];
chars[left++] = chars[right];
chars[right--] = tmp;
}
return new String(chars);
}
二、Java 高级
11. HashMap 底层
JDK7: 数组 + 链表(头插法)→ 并发扩容可能死循环
JDK8: 数组 + 链表 + 红黑树(尾插法)
put 流程:
1. hash(key) 定位桶
2. 桶空 → 直接插入
3. 桶非空 → 比较 key,相同覆盖,不同链表/红黑树追加
4. 超过阈值(8)且数组≥64 → 链表树化
5. 超过负载因子 → 扩容 2 倍
12. ConcurrentHashMap
JDK7: Segment 分段锁(分段数组,每段一把锁)
JDK8: CAS + synchronized(锁桶头节点,粒度更细)
读操作不加锁,写操作对桶加锁
13. 线程池参数
ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程空闲存活时间
workQueue, // 阻塞队列
threadFactory, // 线程工厂
handler // 拒绝策略
)
任务执行流程:
1. 线程 < corePoolSize → 新建线程执行
2. 线程 ≥ corePoolSize → 入队列
3. 队列满 & 线程 < max → 新建非核心线程
4. 队列满 & 线程 = max → 拒绝策略
拒绝策略:
- AbortPolicy(默认)→ 抛异常
- CallerRunsPolicy → 调用线程自己执行
- DiscardPolicy → 静默丢弃
- DiscardOldestPolicy → 丢弃最老的任务
14. JMM 三大特性
原子性:synchronized、Lock、Atomic 类保证
可见性:volatile、synchronized 保证(直接写主存)
有序性:volatile(禁止重排)、synchronized(同一锁内串行)
15. DCL 单例
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 1. 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 2. 第二次检查
instance = new Singleton(); // 3. 必须 volatile!
}
}
}
return instance;
}
}
为什么要 volatile:
new Singleton() 不是原子操作:
- 分配内存
- 初始化对象 ← 这两步可能重排
- 引用赋值 → 不加 volatile 可能拿到"半初始化"的对象
16. 深拷贝 / 浅拷贝
引用拷贝:b = a → 同一个对象
浅拷贝: b = a.clone() → 新对象,内部引用相同
深拷贝: 手动递归复制 → 从外到内全部独立
三、Spring
17. IOC / AOP
IOC(控制反转):对象创建由 Spring 容器管理,不用自己 new
DI(依赖注入):IOC 的实现方式,运行时注入依赖
AOP(面向切面):把日志/事务/鉴权从业务代码抽离
18. Bean 生命周期
1. 实例化
2. 属性填充(依赖注入)
3. Aware 接口回调(BeanNameAware、ApplicationContextAware 等)
4. BeanPostProcessor#postProcessBeforeInitialization
5. @PostConstruct / InitializingBean
6. BeanPostProcessor#postProcessAfterInitialization
7. Bean 就绪,可使用
8. @PreDestroy / DisposableBean → 销毁
19. @Transactional 失效场景
- 方法不是
public - 同类内部调用(
this.xxx())绕过代理 - 异常被 try-catch 吃掉
- 抛出的不是 RuntimeException(默认只在 RuntimeException 回滚)
- Bean 没被 Spring 管理
20. Spring MVC 流程
请求 → DispatcherServlet → HandlerMapping(找 Controller)
→ HandlerAdapter(调用方法)→ 返回 ModelAndView / @ResponseBody
→ ViewResolver(解析视图)→ 响应
21. Spring Boot 自动配置
@SpringBootApplication → @EnableAutoConfiguration
→ 加载 META-INF/spring.factories 中的 AutoConfiguration 类
→ 按 @Conditional 条件判断(有对应类才生效)
四、JVM
22. JVM 内存结构
线程私有:程序计数器、虚拟机栈、本地方法栈
线程共享:堆、方法区(元空间)
23. GC 算法
| 算法 | 逻辑 | 适用 |
|---|---|---|
| 标记-清除 | 标记存活→清除死亡 | 老年代(有碎片) |
| 复制 | 分成两半,存活移到另一半 | 新生代 |
| 标记-整理 | 标记存活→向一端移动 | 老年代(无碎片) |
分代回收思路:
- 新生代:复制算法(存活率低,效率高)
- 老年代:标记-清除 / 标记-整理(存活率高)
24. 类加载机制
加载 → 链接(验证→准备→解析)→ 初始化 → 使用 → 卸载
双亲委派:
加载一个类时,先交给父 ClassLoader
父找不到才自己加载
目的:防止核心 API 被替换(如自己写 java.lang.String)