Java 常见面试问题全解

📚 Java 基础

1. Java 的特点有哪些?

了解 Java 语言的核心特征

  • 面向对象:支持封装、继承、多态等特性
  • 平台独立性:Write Once, Run Anywhere (WORA)
  • 自动内存管理:垃圾回收机制自动释放内存
  • 多线程支持:内置线程机制,便于并发编程
  • 安全性强:提供类型检查、异常处理等安全机制
  • 动态特性:运行时类型检查和动态加载

2. JDK、JRE、JVM 有什么区别?

理解 Java 运行环境的三层结构

  • JVM (Java Virtual Machine):虚拟机,执行字节码的抽象计算机,实现跨平台特性
  • JRE (Java Runtime Environment):运行环境,包含 JVM 和运行必需的类库
  • JDK (Java Development Kit):开发工具包,包含 JRE + 编译器 + 调试工具等开发工具
  • 包含关系:JDK > JRE > JVM

3. 什么是字节码?为什么 Java 能跨平台?

深理解 Java 跨平台机制的原理

  • 字节码定义:Java 源代码编译生成的中间代码,文件扩展名为 .class
  • 跨平台原理:Java 源代码 → 字节码 → 不同平台的 JVM 执行
  • 优势:一次编译,到处运行;开发者无需针对不同平台重新编写代码
  • 执行流程:javac 编译器将 .java 文件编译为 .class 文件,再由各平台 JVM 解释执行

4. Java 中的基本数据类型有哪些?

掌握 Java 的类型系统

  • 整数型:byte (1字节), short (2字节), int (4字节), long (8字节)
  • 浮点型:float (4字节), double (8字节)
  • 字符型:char (2字节,支持 Unicode)
  • 布尔型:boolean (1字节,true/false)
  • 默认值:整数 0,浮点 0.0,布尔 false,char '\u0000'
  • 包装类:Integer, Long, Float, Double, Boolean 等对应的引用类型

5. final、finally、finalize 的区别?

区分三个相似但完全不同的概念

  • final (关键字):修饰类不能继承,修饰方法不能重写,修饰变量不能重新赋值
  • finally (关键字):try-catch-finally 中的代码块,无论是否异常都会执行,常用于资源释放
  • finalize (方法):Object 类的方法,对象被垃圾回收前调用,用于清理资源(已过时)
  • 应用场景:final 用于不变性控制,finally 用于异常安全,finalize 已被 try-with-resources 替代

🎯 面向对象编程

6. 什么是面向对象?有哪四大特性?

理解 OOP 的核心理念

  • 面向对象定义:以对象为单位的编程思想,强调对象的属性和行为
  • 四大特性:
  • 抽象:提取事物的共同特征,忽略非本质细节
  • 封装:隐藏内部实现细节,暴露必要接口,提高安全性
  • 继承:子类继承父类的属性和方法,实现代码复用
  • 多态:同一接口的不同实现,运行时动态绑定

7. 什么是多态?多态的实现方式有哪些?

深入理解 Java 多态机制

  • 多态定义:一个对象拥有多种形态,同一方法调用在不同对象上有不同表现
  • 实现方式:
  • 编译时多态(重载):同名方法,参数不同,编译期确定调用
  • 运行时多态(重写):父类引用指向子类对象,运行期确定实际调用的方法
  • 运行时多态条件:继承、重写、向上转型
  • 优势:提高代码灵活性和可维护性,支持接口编程

8. 接口和抽象类的区别?

对比两种抽象机制的异同

  • 抽象类:使用 abstract 修饰,可有抽象方法和具体方法
  • 接口:使用 interface 定义,默认方法为 public abstract(Java 8+ 支持默认实现)
  • 继承关系:类只能单继承抽象类,但可实现多个接口
  • 访问修饰符:抽象类可用 private/protected,接口成员默认 public
  • 变量:抽象类有实例变量,接口只有静态常量
  • 使用场景:抽象类用于共享代码,接口定义规范

9. 什么是重载(Overload)和重写(Override)?

区分两个相似但不同的概念

  • 重载 (Overload):
  • 同一类中,方法名相同,参数类型/个数/顺序不同
  • 编译时确定,属于静态绑定
  • 返回值类型可以不同(但不能仅通过返回值区分)
  • 重写 (Override):
  • 子类重新实现父类的方法,方法签名完全相同
  • 运行时确定,属于动态绑定
  • 返回值类型和异常必须兼容

10. 访问修饰符有哪些?作用范围是什么?

掌握 Java 的访问控制机制

  • public:公有,所有类都可访问
  • protected:受保护,同包和子类可访问
  • default (无修饰符):默认,同包可访问
  • private:私有,仅类内部可访问
  • 访问范围对比:public > protected > default > private
  • 最佳实践:最小化访问权限,遵循封装原则

💾 内存管理与垃圾回收

11. Java 内存结构包括哪些?

理解 JVM 的内存分配

  • 堆 (Heap):存储对象实例,被所有线程共享,垃圾回收的主要区域,可配置大小
  • 栈 (Stack):存储局部变量和方法调用,每个线程独有,自动释放内存
  • 方法区 (Method Area):存储类的结构信息、运行时常量池、静态变量等,被所有线程共享
  • 程序计数器:记录当前线程执行的字节码指令地址
  • 本地方法栈:执行本地方法(C/C++ 代码)

12. 什么是垃圾回收?垃圾回收算法有哪些?

深入理解 GC 机制

  • 垃圾回收定义:自动回收不再使用的对象占用的内存
  • 主要算法:
  • 标记清除:标记存活对象,清除垃圾,产生碎片
  • 复制算法:分两块内存,清除时复制存活对象,无碎片但浪费内存
  • 标记整理:标记后压缩整理,无碎片但性能较差
  • 分代算法:对象分为新生代和老生代,不同代使用不同策略
  • 优点:自动内存管理,避免内存泄漏

13. 堆和栈的区别?

对比两个重要的内存区域

  • 存储内容:堆存对象,栈存基本类型和引用
  • 线程:堆被所有线程共享,每个线程有独立栈
  • 管理:堆由垃圾回收器管理,栈自动释放
  • 大小:堆一般较大,栈相对较小
  • 性能:栈分配速度快,堆分配相对慢
  • 异常:堆溢出 OutOfMemoryError,栈溢出 StackOverflowError

14. 什么是内存泄漏?如何避免?

认识常见的内存问题

  • 内存泄漏定义:程序申请的内存无法被释放,长期占用内存
  • 常见原因:
  • 长生命周期对象引用短生命周期对象
  • 集合中的对象未及时清理
  • 监听器或回调未取消注册
  • 静态集合无限增长
  • 避免方法:及时释放引用、使用 try-with-resources、定期检查内存使用

15. 什么是强引用、软引用、弱引用、虚引用?

理解 Java 的四种引用类型

  • 强引用:普通引用,对象不被回收,直到无强引用
  • 软引用 (SoftReference):内存不足时被回收,用于缓存
  • 弱引用 (WeakReference):下次 GC 时被回收,用于 WeakHashMap
  • 虚引用 (PhantomReference):任何时候都可被回收,必须与引用队列使用,用于跟踪对象回收
  • 回收优先级:虚引用 > 弱引用 > 软引用 > 强引用

📦 集合框架

16. Java 集合框架的结构?

掌握集合框架的整体概念

  • Collection 接口:
  • List:有序可重复,如 ArrayList、LinkedList
  • Set:无序不重复,如 HashSet、TreeSet
  • Queue:队列,如 LinkedList、PriorityQueue
  • Map 接口:
  • 键值对映射:HashMap、TreeMap、ConcurrentHashMap
  • 整体层次:Iterable → Collection/Map → 具体实现类

17. ArrayList 和 LinkedList 的区别?

对比两种常用列表实现

  • 数据结构:ArrayList 基于数组,LinkedList 基于双向链表
  • 随机访问:ArrayList O(1),LinkedList O(n)
  • 插入删除:ArrayList O(n),LinkedList O(1)
  • 内存占用:ArrayList 连续,LinkedList 分散(指针开销)
  • 线程安全:都不同步,可用 Collections.synchronizedList() 或 CopyOnWriteArrayList
  • 选择:频繁查询用 ArrayList,频繁插删用 LinkedList

18. HashMap 的原理和性能?

深入理解 HashMap 的工作机制

  • 数据结构:数组 + 链表 + 红黑树(JDK 8+)
  • 工作原理:hash(key) % table.length 计算数组下标,冲突时链接或树化
  • 加载因子:默认 0.75,当已用容量 ≥ 容量 × 加载因子时扩容
  • 扩容机制:容量翻倍,元素重新哈希定位
  • 时间复杂度:平均 O(1),最差 O(n)(大量冲突时)
  • 线程安全:不同步,多线程用 ConcurrentHashMap 或 Collections.synchronizedMap()

19. HashSet 如何保证不重复?

理解 Set 的去重机制

  • 底层实现:基于 HashMap,key 为元素,value 为固定 Object
  • 去重机制:先比较 hashCode(),再用 equals() 判断相等
  • 添加流程:计算 hash → 检查是否存在 → 不存在添加 → 存在则忽略
  • 自定义对象:必须重写 equals() 和 hashCode(),保证一致性
  • 性能:添加、删除、查找平均 O(1),取决于 hash 质量

20. fail-fast 和 fail-safe 是什么?

理解集合的迭代器安全机制

  • fail-fast:
  • 迭代过程中修改集合会抛 ConcurrentModificationException
  • 通过 modCount 和 expectedModCount 检测
  • 如 ArrayList、HashMap(非线程安全)
  • fail-safe:
  • 迭代基于集合的快照或副本,修改原集合不影响迭代
  • 如 CopyOnWriteArrayList、ConcurrentHashMap
  • 使用建议:迭代时用迭代器的 remove(),或用 fail-safe 集合

⚠️ 异常处理

21. Java 异常体系?

理解 Java 异常的分类

  • Throwable(根类):
  • Exception(异常):可恢复的异常
  • Error(错误):虚拟机级别错误,不可恢复
  • Exception 分类:
  • 检查异常 (Checked):必须捕获或声明,如 IOException
  • 非检查异常 (Unchecked):可不捕获,如 NullPointerException、IndexOutOfBoundsException
  • 常见异常:NPE、ClassCastException、ArrayIndexOutOfBoundsException 等

22. try-catch-finally 执行顺序?

掌握异常处理的执行流程

  • 正常情况:try → finally → 正常返回
  • 异常情况:try → 异常发生 → catch → finally → 异常传递或返回
  • finally 特性:必定执行,即使 catch 中 return、throw、System.exit()
  • 例外情况:finally 中 return 会覆盖 try/catch 的 return
  • 资源释放:推荐使用 try-with-resources,自动关闭资源
  • 最佳实践:不要在 finally 中改变返回值,会导致异常丢失

23. throws 和 throw 的区别?

区分两个异常处理关键字

  • throw:
  • 手动抛出异常实例,必须在方法内使用
  • 格式:throw new Exception("message")
  • throws:
  • 在方法签名中声明可能抛出的异常
  • 格式:public void method() throws IOException
  • 处理方式:throw 由 throws 声明,throws 由调用者处理
  • 应用:throw 用于具体异常处理,throws 用于异常传递

24. 如何自定义异常?

创建项目特定的异常类

  • 继承关系:继承 Exception(检查异常)或 RuntimeException(非检查异常)
  • 必要部分:
  • 提供无参构造器
  • 提供含 message 的构造器
  • 提供含 message 和 cause 的构造器
  • 代码示例:public class CustomException extends Exception { ... }
  • 最佳实践:明确命名,清晰文档,继承合适的异常类

25. try-with-resources 语法的好处?

理解资源自动管理

  • 语法:try (InputStream is = ...) { ... } 自动关闭资源
  • 要求:资源必须实现 AutoCloseable 接口
  • 优势:
  • 自动调用 close() 方法,无需手动管理
  • 异常被压制时能正确处理
  • 代码更简洁,避免资源泄漏
  • 适用场景:File、Stream、Connection、Statement 等资源类

🔤 String、StringBuilder、StringBuffer

26. String 的不可变性及其原因?

理解 String 设计的深层考虑

  • 不可变定义:String 对象创建后无法被修改,任何修改操作返回新对象
  • 实现方式:value 数组被 final 修饰,无 setter 方法
  • 不可变原因:
  • 字符串缓冲池优化,避免重复创建
  • 线程安全,无需同步
  • 支持 hashCode 缓存,适合做 HashMap key
  • 缺点:频繁修改时会创建大量中间对象

27. String、StringBuilder、StringBuffer 的区别?

选择合适的字符串操作类

  • String:不可变,线程安全,性能差(频繁修改)
  • StringBuilder:可变,非线程安全,性能优(单线程)
  • StringBuffer:可变,线程安全(同步方法),性能一般(多线程)
  • 性能对比:StringBuilder > StringBuffer > String
  • 使用建议:
  • 单线程字符串拼接用 StringBuilder
  • 多线程字符串拼接用 StringBuffer
  • 不需修改用 String

28. 字符串常量池是什么?

理解字符串缓存机制

  • 定义:JVM 维护的字符串缓存,存储字符串字面量
  • 位置:JDK 7+ 在堆中,之前在方法区
  • 创建机制:
  • 字面量如 "abc" 自动进入常量池
  • new String("abc") 若 "abc" 不在池中则添加
  • intern() 方法:将字符串添加到常量池或返回现有引用
  • 优化效果:节省内存,加快字符串比较

29. 如何比较字符串相等?

掌握字符串比较的方法

  • == 比较:比较引用是否相同,不比较内容
  • equals() 比较:比较字符串内容是否相同,推荐使用
  • equalsIgnoreCase() 比较:忽略大小写比较内容
  • compareTo() 比较:字典序比较,返回整数
  • Objects.equals():处理 null 值的安全比较
  • 最佳实践:比较字符串内容用 equals(),避免用 ==

30. String 拼接的性能问题?

优化字符串拼接的性能

  • 问题:String s = "a" + "b" + "c" 产生多个中间对象和复制
  • 原因:String 不可变,每次拼接都创建新对象
  • 性能影响:循环拼接时 O(n²) 时间复杂度
  • 优化方案:
  • 直接 + 在循环外:编译器会优化为 StringBuilder
  • 显式使用 StringBuilder 或 StringBuffer
  • 使用 String.join()、StringJoiner 等工具

🔄 多线程与并发

31. 什么是线程?如何创建线程?

掌握线程的基本概念和创建方式

  • 线程定义:进程内独立执行流,共享内存但有独立栈
  • 创建方式:
  • 继承 Thread:public class MyThread extends Thread { public void run() {} }
  • 实现 Runnable:public class MyRunnable implements Runnable { public void run() {} }
  • 实现 Callable:返回值和异常支持
  • 区别:Runnable 推荐,避免单继承限制
  • 启动:调用 thread.start(),不能直接调用 run()

32. 线程的生命周期和状态?

理解线程的各种状态转换

  • NEW:线程创建但未启动
  • RUNNABLE:可运行状态(等待执行或正在执行)
  • BLOCKED:阻塞状态,等待获取锁
  • WAITING:等待状态,等待其他线程通知
  • TIMED_WAITING:等待指定时间
  • TERMINATED:线程终止
  • 状态转换:NEW → RUNNABLE → BLOCKED/WAITING → TERMINATED

33. synchronized 的工作原理?

理解 Java 的内置锁机制

  • 同步机制:使用对象的内置锁实现同步
  • 使用方式:
  • 同步方法:public synchronized void method() {}
  • 同步块:synchronized(obj) { ... }
  • 工作原理:
  • 每个对象都有一个监视器 (monitor)
  • 线程获取锁进入临界区,互斥执行
  • 字节码:monitorenter、monitorexit
  • 特性:可重入,排他,自动释放

34. volatile 关键字的作用?

理解内存可见性和禁止指令重排

  • 作用:
  • 可见性:保证修改对其他线程立即可见
  • 禁止重排:阻止编译器和 CPU 重排优化
  • 实现:内存屏障,强制从主存读写
  • 限制:不能保证原子性,不能替代 synchronized
  • 应用场景:标志位、双检查单例、状态标记
  • 对比 synchronized:volatile 更轻量但功能受限

35. wait()、notify()、notifyAll() 的作用和区别?

掌握线程间通信机制

  • wait():
  • 当前线程释放锁进入等待,直到被唤醒
  • 必须在同步块内调用
  • notify():
  • 唤醒一个等待线程(随机选择)
  • 不释放锁,等同步块结束才释放
  • notifyAll():
  • 唤醒所有等待线程
  • 使用模式:Producer-Consumer 模式、管程模式

💡 面试准备建议

深化基础:不只知道概念,更要理解原理和实现细节

多做练习:通过编码实践巩固知识,特别是多线程和并发相关

阅读源码:研究 Java 库源码(集合、并发等)加深理解

关注细节:掌握常见陷阱和最佳实践,如字符串比较、集合修改等

举例说明:面试时用具体示例说明概念,增强表达力