Java常见八股对比
一、并发相关对比
1. synchronized vs ReentrantLock
| 对比 | synchronized | ReentrantLock |
|---|---|---|
| 所属层面 | JVM 层面 | JDK 层面 |
| 底层 | Monitor 对象监视器 | AQS |
| 加锁方式 | 隐式加锁、自动释放 | 手动 lock/unlock |
| 是否可中断 | 不支持 | 支持 lockInterruptibly |
| 是否支持公平锁 | 不支持 | 支持公平/非公平 |
| 是否支持多个条件队列 | 不支持 | 支持多个 Condition |
面试一句话:
synchronized是 JVM 层面的内置锁,使用简单,自动释放;ReentrantLock是 JDK 层面基于 AQS 实现的显式锁,功能更强,比如支持公平锁、可中断锁、超时获取锁和多个条件队列。
2. volatile vs synchronized
| 对比 | volatile | synchronized |
|---|---|---|
| 解决问题 | 可见性、有序性 | 原子性、可见性、有序性 |
| 是否加锁 | 不加锁 | 加锁 |
| 是否保证复合操作原子性 | 不保证 | 保证 |
| 典型场景 | 状态标记、双重检查锁 | 临界区互斥 |
面试一句话:
volatile只能保证变量修改对其他线程可见,并禁止指令重排序,但不能保证i++这种复合操作的原子性;synchronized可以保证同一时刻只有一个线程进入临界区,所以能保证原子性。
3. sleep vs wait
| 对比 | sleep | wait |
|---|---|---|
| 所属类 | Thread | Object |
| 是否释放锁 | 不释放锁 | 释放锁 |
| 使用位置 | 任意位置 | 必须在 synchronized 内 |
| 唤醒方式 | 时间到自动恢复 | notify/notifyAll 或超时 |
面试一句话:
sleep是线程休眠,不释放锁;wait是对象等待,会释放当前对象锁,必须配合synchronized使用。
4. start vs run
| 对比 | start | run |
|---|---|---|
| 是否创建新线程 | 是 | 否 |
| 执行方式 | JVM 创建新线程后调用 run | 普通方法调用 |
| 场景 | 启动线程 | 不直接用于启动线程 |
面试一句话:
调用
start()才是真正启动一个新线程;直接调用run()只是普通方法调用,不会开启新线程。
5. Runnable vs Callable
| 对比 | Runnable | Callable |
|---|---|---|
| 返回值 | 没有 | 有 |
| 异常 | 不能直接抛受检异常 | 可以抛异常 |
| 方法 | run | call |
| 搭配 | Thread / Executor | Future / ExecutorService |
面试一句话:
Runnable没有返回值,Callable有返回值,并且可以抛异常,通常配合Future获取异步执行结果。
6. execute vs submit
| 对比 | execute | submit |
|---|---|---|
| 所属 | Executor | ExecutorService |
| 返回值 | 无 | 返回 Future |
| 异常表现 | 异常直接抛到线程的 UncaughtExceptionHandler | 异常封装到 Future,get 时抛出 |
| 适合 | 不关心结果 | 关心执行结果 |
面试一句话:
execute只提交任务,不返回结果;submit会返回Future,可以通过get()获取结果或异常。
7. CountDownLatch vs CyclicBarrier vs Semaphore
| 对比 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 作用 | 等多个线程完成 | 多个线程互相等待 | 控制并发许可数 |
| 是否可复用 | 不可复用 | 可复用 | 可复用 |
| 典型场景 | 主线程等子任务完成 | 多线程分阶段同步 | 限流、连接池 |
面试一句话:
CountDownLatch是一个线程等多个线程完成;CyclicBarrier是多个线程互相等待到齐再继续;Semaphore是控制同时访问资源的线程数量。
8. ThreadLocal vs InheritableThreadLocal
| 对比 | ThreadLocal | InheritableThreadLocal |
|---|---|---|
| 作用 | 线程内部变量副本 | 父线程传给子线程 |
| 子线程是否能拿到父线程值 | 不能 | 可以 |
| 线程池场景 | 要注意 remove | 线程池中可能脏数据 |
| 常见场景 | 用户上下文、traceId | 父子线程上下文传递 |
面试一句话:
ThreadLocal是线程隔离变量,子线程默认拿不到父线程的值;InheritableThreadLocal可以在线程创建时继承父线程值,但在线程池复用场景下容易出现脏数据,实际可以用 TTL 这类方案。
二、集合相关对比
9. ArrayList vs LinkedList
| 对比 | ArrayList | LinkedList |
|---|---|---|
| 底层 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 中间插入删除 | 需要移动元素 | 找到节点后改指针 |
| 内存占用 | 较低 | 较高,有 prev/next |
| 适合 | 查询多 | 头尾操作多 |
面试一句话:
ArrayList底层是数组,随机访问快;LinkedList底层是双向链表,按下标查询慢,但头尾插入删除方便。
10. HashMap vs Hashtable vs ConcurrentHashMap
| 对比 | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | 不安全 | 安全 | 安全 |
| 加锁方式 | 无锁 | 方法级 synchronized | JDK8 CAS + synchronized |
| null key/value | 支持一个 null key | 不支持 | 不支持 |
| 性能 | 单线程高 | 较低 | 并发场景高 |
面试一句话:
HashMap线程不安全;Hashtable通过方法级synchronized保证安全,锁粒度粗;ConcurrentHashMap是并发容器,JDK8 中主要通过 CAS 和桶级 synchronized 保证并发安全。
11. HashMap vs TreeMap
| 对比 | HashMap | TreeMap |
|---|---|---|
| 底层 | 哈希表 | 红黑树 |
| 是否有序 | 无序 | 按 key 排序 |
| 查询复杂度 | 平均 O(1) | O(log n) |
| 是否支持范围查询 | 不适合 | 支持 |
面试一句话:
HashMap适合快速查找;TreeMap基于红黑树,适合需要 key 有序或范围查询的场景。
12. HashSet vs TreeSet
| 对比 | HashSet | TreeSet |
|---|---|---|
| 底层 | HashMap | TreeMap |
| 是否有序 | 无序 | 有序 |
| 性能 | 平均 O(1) | O(log n) |
| 去重依据 | hashCode + equals | compareTo / Comparator |
面试一句话:
HashSet基于哈希去重,性能高但无序;TreeSet基于红黑树,能排序,但性能相对低。
三、字符串相关对比
13. String vs StringBuilder vs StringBuffer
| 对比 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 是否可变 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全,因为不可变 | 不安全 | 安全 |
| 加锁 | 无 | 无 | 方法加 synchronized |
| 适合 | 少量字符串 | 单线程大量拼接 | 多线程共享拼接 |
面试一句话:
String不可变,每次修改都会产生新对象;StringBuilder可变但线程不安全,性能高;StringBuffer可变且线程安全,方法上加了synchronized。
14. == vs equals
| 对比 | == | equals |
|---|---|---|
| 基本类型 | 比较值 | 不能直接用 |
| 引用类型 | 比较地址 | 默认比较地址,可重写比较内容 |
| String | 比较引用 | 比较字符串内容 |
面试一句话:
==对基本类型比较值,对引用类型比较地址;equals默认也是比较地址,但很多类比如String重写了 equals,用来比较内容。
15. final vs finally vs finalize
| 对比 | final | finally | finalize |
|---|---|---|---|
| 类型 | 关键字 | 异常处理代码块 | Object 方法 |
| 作用 | 修饰类、方法、变量 | 保证资源清理逻辑执行 | GC 前可能调用 |
| 是否推荐 | 推荐 | 推荐用于清理 | 不推荐,已过时思路 |
面试一句话:
final表示不可变或不能继承/重写;finally用于异常处理中的资源释放;finalize是对象被回收前可能调用的方法,不可靠,实际开发不建议依赖。
四、面向对象相关对比
16. 重载 vs 重写
| 对比 | 重载 Overload | 重写 Override |
|---|---|---|
| 发生位置 | 同一个类 | 父子类 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回值 | 不能仅靠返回值区分 | 相同或协变返回 |
| 多态 | 编译时多态 | 运行时多态 |
面试一句话:
重载是同一个类中方法名相同、参数不同,编译期确定;重写是子类改写父类方法,运行期根据真实对象类型确定调用哪个方法。
17. 抽象类 vs 接口
| 对比 | 抽象类 | 接口 |
|---|---|---|
| 继承关系 | 单继承 | 多实现 |
| 表达含义 | 是什么 | 能做什么 |
| 成员变量 | 可以有普通成员变量 | 默认 public static final |
| 方法 | 可以有普通方法和抽象方法 | 可有抽象方法、默认方法、静态方法 |
| 构造方法 | 有 | 没有 |
面试一句话:
抽象类更适合表达一类对象的公共父类,强调“是什么”;接口更适合定义能力和规范,强调“能做什么”。
18. 普通类 vs 抽象类
| 对比 | 普通类 | 抽象类 |
|---|---|---|
| 是否能实例化 | 能 | 不能 |
| 是否能有抽象方法 | 不能 | 能 |
| 作用 | 直接创建对象 | 提供模板和约束 |
| 典型场景 | 具体业务类 | 模板方法、公共父类 |
面试一句话:
抽象类不能直接实例化,主要用于抽取公共逻辑和定义子类必须实现的行为。
19. this vs super
| 对比 | this | super |
|---|---|---|
| 指向 | 当前对象 | 父类部分 |
| 调用构造 | this(...) | super(...) |
| 调用成员 | 当前类成员 | 父类成员 |
| 使用场景 | 区分成员变量和局部变量 | 调用父类构造或被覆盖方法 |
面试一句话:
this表示当前对象,super表示父类对象部分,常用于调用父类构造方法或父类被重写的方法。
五、Spring 相关对比
20. @Autowired vs @Resource
| 对比 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring | JSR-250 / Jakarta 标准 |
| 默认注入方式 | 按类型 | 按名称 |
| 配合注解 | @Qualifier | name 属性 |
| 是否 Spring 独有 | 是 | 否,Spring 支持它 |
面试一句话:
@Autowired是 Spring 的注解,默认按类型注入;@Resource是 Java/Jakarta 标准注解,默认按名称注入,找不到名称时再按类型匹配。
21. @Component vs @Bean
| 对比 | @Component | @Bean |
|---|---|---|
| 作用位置 | 类上 | 方法上 |
| 创建方式 | Spring 扫描后自动创建 | 调用方法,把返回值放进容器 |
| 适合 | 自己写的类 | 第三方类、复杂初始化对象 |
| 控制能力 | 较弱 | 更强 |
面试一句话:
@Component用在类上,适合把自己写的类交给 Spring;@Bean用在方法上,适合把第三方对象或复杂初始化对象交给 Spring。
22. @Controller vs @RestController
| 对比 | @Controller | @RestController |
|---|---|---|
| 用途 | 返回页面或数据 | 返回 JSON 数据 |
| 是否包含 @ResponseBody | 不包含 | 包含 |
| 典型场景 | MVC 页面 | 前后端分离接口 |
面试一句话:
@RestController = @Controller + @ResponseBody,默认把方法返回值写入 HTTP 响应体,常用于返回 JSON。
23. BeanFactory vs ApplicationContext
| 对比 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 基础 IOC 容器 | 完整应用上下文 |
| 功能 | 创建和管理 Bean | IOC + AOP + 事件 + 国际化 + 资源加载 |
| 初始化 | 懒加载为主 | 默认启动时创建单例 Bean |
| 使用场景 | Spring 底层 | 实际开发 |
面试一句话:
BeanFactory是 Spring 最基础的 IOC 容器;ApplicationContext是它的增强版,提供事件、国际化、资源加载、AOP 等功能,实际开发中基本使用ApplicationContext。
24. BeanFactory vs FactoryBean
| 对比 | BeanFactory | FactoryBean |
|---|---|---|
| 是什么 | 容器 | 一个特殊 Bean |
| 作用 | 管理 Bean | 自定义 Bean 创建逻辑 |
| 典型场景 | Spring 容器核心接口 | MyBatis Mapper 代理对象创建 |
面试一句话:
BeanFactory是 Spring 容器;FactoryBean是一种特殊 Bean,用来封装复杂对象的创建过程。
25. JDK 动态代理 vs CGLIB
| 对比 | JDK 动态代理 | CGLIB |
|---|---|---|
| 底层 | 反射 + InvocationHandler | 字节码增强 + 继承 |
| 是否需要接口 | 需要 | 不需要 |
| 代理方式 | 生成接口实现类 | 生成目标类子类 |
| final 类/方法 | 不影响接口代理 | final 不能代理 |
面试一句话:
JDK 动态代理基于接口,CGLIB 基于继承。Spring AOP 原生默认有接口用 JDK 动态代理,没有接口用 CGLIB;Spring Boot 默认通常倾向 CGLIB。
26. Spring MVC Interceptor vs Filter
| 对比 | Filter | Interceptor |
|---|---|---|
| 所属 | Servlet 规范 | Spring MVC |
| 执行位置 | DispatcherServlet 之前 | DispatcherServlet 之后、Controller 之前 |
| 拦截范围 | 所有 Web 请求 | Spring MVC 请求 |
| 能否拿到 Handler | 不方便 | 可以 |
| 典型场景 | 编码、跨域、安全过滤 | 登录、权限、日志、traceId |
面试一句话:
Filter 是 Servlet 层面的过滤器,执行更靠前;Interceptor 是 Spring MVC 层面的拦截器,能拿到具体 handler,更适合做业务拦截。
六、JVM / 类加载相关对比
27. 堆 vs 栈
| 对比 | 堆 | 栈 |
|---|---|---|
| 存什么 | 对象实例、数组 | 方法调用栈帧、局部变量 |
| 线程共享 | 共享 | 线程私有 |
| GC | 主要区域 | 方法结束自动出栈 |
| 异常 | OOM | StackOverflowError |
面试一句话:
堆是线程共享的,主要存对象;栈是线程私有的,存方法调用过程中的栈帧和局部变量。
28. 方法区 vs 元空间
| 对比 | 方法区 | 元空间 |
|---|---|---|
| 含义 | JVM 规范中的逻辑区域 | HotSpot 对方法区的实现 |
| JDK7 | 永久代实现 | 无 |
| JDK8+ | 元空间实现 | 使用本地内存 |
| 存储 | 类元信息、常量、静态变量引用等 | 类元信息为主 |
面试一句话:
方法区是 JVM 规范概念,JDK8 以后 HotSpot 用元空间实现方法区,元空间使用本地内存,不再使用永久代。
29. 类加载器双亲委派 vs 打破双亲委派
| 对比 | 双亲委派 | 打破双亲委派 |
|---|---|---|
| 默认机制 | 先让父加载器加载 | 子加载器优先或自定义加载 |
| 目的 | 防止核心类被篡改 | 满足隔离、热部署、SPI |
| 典型 | java.lang.String 由 Bootstrap 加载 | Tomcat、JDBC SPI |
面试一句话:
双亲委派是类加载时先委托父加载器,保证核心类安全;Tomcat、SPI 等场景会打破双亲委派,实现类隔离或反向加载。
七、异常相关对比
30. Exception vs Error
| 对比 | Exception | Error |
|---|---|---|
| 含义 | 程序可处理异常 | JVM 或系统级严重错误 |
| 是否建议捕获 | 可以 | 一般不建议 |
| 例子 | IOException、SQLException | OOM、StackOverflowError |
面试一句话:
Exception通常是程序可处理的异常;Error表示严重问题,比如 OOM、栈溢出,一般不建议业务代码捕获。
31. checked exception vs unchecked exception
| 对比 | checked exception | unchecked exception |
|---|---|---|
| 编译期检查 | 是 | 否 |
| 是否必须处理 | 必须 catch 或 throws | 不强制 |
| 例子 | IOException | NullPointerException |
面试一句话:
受检异常编译期必须处理;非受检异常一般是运行期异常,不强制捕获。
八、IO 相关对比
32. BIO vs NIO vs AIO
| 对比 | BIO | NIO | AIO |
|---|---|---|
| 模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
| 线程模型 | 一个连接一个线程 | 一个线程处理多个连接 | 操作系统完成后回调 |
| 核心 | Stream | Channel、Buffer、Selector | CompletionHandler |
| 场景 | 连接少 | 高并发网络 | 异步 IO 场景 |
面试一句话:
BIO 是同步阻塞,一个连接通常占一个线程;NIO 是同步非阻塞,通过 Selector 多路复用;AIO 是异步 IO,操作系统完成后回调通知。
33. InputStream vs Reader
| 对比 | InputStream | Reader |
|---|---|---|
| 处理单位 | 字节 | 字符 |
| 适合 | 图片、文件、二进制 | 文本 |
| 编码处理 | 不处理字符编码 | 处理字符编码 |
面试一句话:
InputStream面向字节,适合二进制数据;Reader面向字符,适合文本数据,会涉及字符编码。
更多推荐
所有评论(0)