Java基础面试八股文精讲(2026最新版)
这里有🔥Java基础面试八股文精讲(2026最新版)| 大厂高频必考,含详细答案
作者前言:我是一名软件工程大三学生,备战暑期实习期间整理了这份Java基础面试题合集。这些题目都是我在牛客、各大面经社区反复出现的高频题,每道题我都尽量用最通俗的语言讲清楚,避免"背了不懂、懂了记不住"的问题。建议收藏,面试前反复过一遍。
目录
1. Java 中 == 和 equals() 的区别
这是面试最高频的送分题,一定要答得清楚。
== 运算符
- 对于基本数据类型(int、char、double 等):比较的是值是否相等
- 对于引用数据类型(对象):比较的是两个引用是否指向同一块内存地址
equals() 方法
- 是 Object 类的方法,默认实现和
==一样(比较地址) - 但 String、Integer 等类重写了 equals(),改为比较内容是否相等
代码演示
public class EqualsDemo {
public static void main(String[] args) {
// 基本类型:== 比较值
int a = 10;
int b = 10;
System.out.println(a == b); // true
// 引用类型:== 比较地址
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(两个不同对象)
System.out.println(s1.equals(s2)); // true(String重写了equals,比较内容)
// 字符串常量池的特殊情况
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // true(同一个常量池对象)
}
}
总结一句话
==比地址,equals()比内容(前提是类重写了 equals 方法)。
2. hashCode() 和 equals() 的关系
这道题经常跟上面连着问,要配套回答。
两大约定(必须记住)
- equals() 返回 true,则 hashCode() 必须相同
- hashCode() 相同,equals() 不一定返回 true(哈希碰撞)
为什么要同时重写?
以 HashMap 为例,put 一个 key 时:
- 先计算 key 的
hashCode(),定位到桶的位置 - 再用
equals()判断桶中是否已存在相同的 key
如果只重写 equals() 不重写 hashCode():
- 两个"内容相等"的对象,hashCode 可能不同
- 导致它们被放到不同的桶里,HashMap 认为是两个不同的 key
- 数据错乱!
代码示例
public class Person {
private String name;
private int age;
// 只重写equals,不重写hashCode —— 错误示范!
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return age == other.age && Objects.equals(name, other.name);
}
// 正确做法:equals 和 hashCode 都要重写
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
总结
重写
equals()必须同时重写hashCode(),否则在使用 HashMap、HashSet 等集合时会出现严重 bug。
3. String、StringBuilder、StringBuffer 的区别
字符串三兄弟,面试必考,要说清楚三个维度:可变性、线程安全、性能。
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(synchronized) |
| 性能 | 慢(每次创建新对象) | 最快 | 比StringBuilder慢 |
| 使用场景 | 少量字符串操作 | 单线程大量拼接 | 多线程大量拼接 |
String 为什么不可变?
public final class String {
// value 数组是 private final 的,且 String 类本身是 final 的
private final char value[];
}
两个关键点:
char[]被声明为final,引用不可更改- String 类被
final修饰,不能被继承,从根本上防止了被子类破坏不可变性
性能对比
// 低效写法:每次拼接都会产生新的 String 对象
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 实际上创建了大量临时对象
}
// 高效写法:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result2 = sb.toString();
总结
单线程拼接用 StringBuilder,多线程用 StringBuffer,字符串内容不变用 String。
4. Java 中的四种引用类型
JVM 调优和内存管理相关题目的前置知识,中级面试必考。
Java 提供四种引用强度,配合 GC(垃圾回收)使用:
1. 强引用(Strong Reference)
最普通的引用,只要强引用存在,对象永远不会被 GC 回收。
Object obj = new Object(); // 强引用
2. 软引用(Soft Reference)
内存充足时不回收,内存不足时才回收。适合做缓存。
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024 * 1024]);
byte[] data = softRef.get(); // 可能为 null(已被GC)
3. 弱引用(Weak Reference)
只要发生 GC,无论内存是否充足,一律回收。ThreadLocal 中使用了弱引用。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
System.gc();
System.out.println(weakRef.get()); // null
4. 虚引用(Phantom Reference)
最弱的引用,完全无法通过它获取对象,仅用于跟踪对象被 GC 的时机,配合引用队列使用。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
System.out.println(phantomRef.get()); // 永远是 null
总结表格
| 引用类型 | 回收时机 | 使用场景 |
|---|---|---|
| 强引用 | 永不回收 | 普通对象 |
| 软引用 | 内存不足时 | 图片缓存、对象池 |
| 弱引用 | 每次GC | ThreadLocal、WeakHashMap |
| 虚引用 | 每次GC | 监控对象回收 |
5. 接口和抽象类的区别
OOP 核心概念,设计模式的基础,必须答得有深度。
语法层面的区别
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class | interface |
| 继承/实现 | 单继承 | 可实现多个接口 |
| 构造方法 | 可以有 | 没有 |
| 成员变量 | 任意类型 | 只能是 public static final |
| 普通方法 | 可以有方法体 | Java8前不能,Java8后可以有default方法 |
| 访问修饰符 | 任意 | 方法默认 public |
设计层面的区别(这才是面试官想听的)
-
抽象类:表示"is-a"关系。用于有公共代码、公共属性的类族。
- 例:
Animal抽象类,Dog和Cat继承它,都是动物。
- 例:
-
接口:表示"can-do"关系,是一种能力或协议。
- 例:
Flyable接口,Bird和Airplane都可以实现,但它们不是同一类东西。
- 例:
// 抽象类:模板方法模式的核心
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法,子类必须实现
public abstract void sound();
// 普通方法,子类直接继承
public void eat() {
System.out.println(name + " is eating");
}
}
// 接口:定义行为能力
public interface Swimmable {
void swim();
}
public interface Flyable {
void fly();
}
// 一个类可以实现多个接口
public class Duck extends Animal implements Swimmable, Flyable {
public Duck() { super("Duck"); }
@Override public void sound() { System.out.println("Quack"); }
@Override public void swim() { System.out.println("Duck is swimming"); }
@Override public void fly() { System.out.println("Duck is flying"); }
}
总结
抽象类用于代码复用和模板设计,接口用于定义规范和实现多态。优先使用接口,因为 Java 是单继承的。
6. final、finally、finalize 的区别
三个长得像的关键字,考察基础细心程度,送分题。
final
修饰符,用于限制"变化":
// 修饰类:不能被继承
public final class String { ... }
// 修饰方法:不能被重写
public final void show() { ... }
// 修饰变量:只能赋值一次(引用不可变,但对象内容可变)
final int MAX = 100;
final List<String> list = new ArrayList<>();
list.add("hello"); // 合法!引用没变,内容可以变
// list = new ArrayList<>(); // 非法!引用不能变
finally
异常处理中的代码块,一定会执行(除非 JVM 崩溃或 System.exit()):
try {
int result = 10 / 0; // 抛出异常
} catch (ArithmeticException e) {
System.out.println("捕获异常:" + e.getMessage());
} finally {
System.out.println("我一定会执行!"); // 无论如何都执行
// 常用于关闭资源:connection.close(), stream.close()
}
finalize
Object 类的方法,在对象被 GC 回收前调用。
@Override
protected void finalize() throws Throwable {
System.out.println("对象被回收了");
super.finalize();
}
⚠️ 注意:finalize() 已在 Java 9 中被标记为 @Deprecated,Java 18 中删除。不推荐使用,了解即可。
一句话总结
final是修饰符(限制变化),finally是代码块(必执行),finalize是方法(GC前调用,已废弃)。
7. Java 异常体系详解
项目开发必用,面试必考,要能画出继承关系。
异常体系结构
Throwable
├── Error(严重错误,程序无法处理)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception(程序可以处理的异常)
├── RuntimeException(运行时异常,非受检异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── ArithmeticException
│ └── ...
└── 受检异常(编译器强制要求处理)
├── IOException
├── SQLException
└── ...
受检异常 vs 非受检异常
| 类型 | 示例 | 是否必须处理 |
|---|---|---|
| 受检异常(Checked) | IOException, SQLException | 必须 try-catch 或 throws 声明 |
| 非受检异常(Unchecked) | NullPointerException, ArrayIndexOutOfBoundsException | 不强制处理 |
异常处理最佳实践
// ❌ 错误做法:捕获了异常但什么都不做
try {
// ...
} catch (Exception e) {
// 空catch,吃掉了异常,排查问题时会很痛苦
}
// ✅ 正确做法:至少要打印日志
try {
File file = new File("test.txt");
FileReader reader = new FileReader(file);
} catch (FileNotFoundException e) {
log.error("文件未找到:{}", e.getMessage(), e);
throw new BusinessException("文件处理失败", e); // 包装成业务异常抛出
} finally {
// 关闭资源
}
// ✅ 更优雅的做法:try-with-resources(Java7+)
try (FileReader reader = new FileReader("test.txt")) {
// reader 会自动关闭
} catch (IOException e) {
log.error("IO异常", e);
}
自定义异常
// 自定义业务异常
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = 500;
}
public Integer getCode() { return code; }
}
8. 深拷贝和浅拷贝的区别
对象复制问题,项目中很容易踩坑。
概念
- 浅拷贝:创建新对象,但对象内部的引用类型属性仍指向原来的对象
- 深拷贝:创建新对象,内部所有引用类型属性也递归地创建新对象
代码演示
// 浅拷贝示例
public class Address implements Cloneable {
String city;
public Address(String city) { this.city = city; }
}
public class Person implements Cloneable {
String name;
Address address; // 引用类型
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// 浅拷贝:只复制了引用
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 深拷贝:递归复制引用类型
public Person deepClone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = new Address(this.address.city); // 创建新的Address对象
return cloned;
}
}
// 测试
Person p1 = new Person("张三", new Address("北京"));
Person p2 = (Person) p1.clone(); // 浅拷贝
Person p3 = p1.deepClone(); // 深拷贝
p2.address.city = "上海";
System.out.println(p1.address.city); // 上海!p1被影响了(浅拷贝问题)
p3.address.city = "广州";
System.out.println(p1.address.city); // 上海,p1没被影响(深拷贝安全)
实际开发中的深拷贝方式
- 序列化/反序列化(最简单)
- JSON 转换:
Object obj2 = JSON.parseObject(JSON.toJSONString(obj1), obj1.getClass()) - 手动递归复制(如上面的 deepClone)
- Apache Commons Lang:
SerializationUtils.clone(obj)
9. Java 中的自动装箱与拆箱
看起来简单,但有坑!Integer 缓存池是经典考点。
什么是装箱/拆箱
// 装箱:基本类型 → 包装类型(自动调用 Integer.valueOf())
Integer a = 100; // 等价于 Integer a = Integer.valueOf(100);
// 拆箱:包装类型 → 基本类型(自动调用 intValue())
int b = a; // 等价于 int b = a.intValue();
Integer 缓存池陷阱(高频考点!)
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false !!!
原因:Integer 内部有一个缓存池,默认缓存 -128 到 127 之间的值。
- 127 在范围内,
Integer.valueOf(127)返回同一个缓存对象,所以==为 true - 128 超出范围,每次都新建对象,所以
==为 false
// Integer 源码(简化)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; // 返回缓存
return new Integer(i); // 超出范围,新建
}
自动拆箱的 NullPointerException 坑
Integer count = null;
int result = count + 1; // 拆箱时调用 count.intValue() → NullPointerException!
总结
包装类型比较大小用
equals(),不要用==!注意 null 的拆箱会抛 NPE。
10. 为什么 Java 中只有值传递?
这道题很多人答错,需要想清楚。
结论先说
Java 中方法参数传递只有值传递,没有引用传递。
基本类型参数
public static void change(int x) {
x = 100;
}
int a = 10;
change(a);
System.out.println(a); // 10,没变!传的是 a 的值的副本
引用类型参数(容易误解的地方)
public static void modifyContent(StringBuilder sb) {
sb.append(" World"); // 修改了对象内容
}
public static void modifyReference(StringBuilder sb) {
sb = new StringBuilder("New Object"); // 只改了局部变量,对外无效
}
StringBuilder s = new StringBuilder("Hello");
modifyContent(s);
System.out.println(s); // "Hello World"(对象内容被修改了)
modifyReference(s);
System.out.println(s); // "Hello World"(引用没变)
理解关键
传引用类型时,传递的是引用的值(内存地址的副本):
- 通过这个地址副本,可以修改它指向的对象的内容
- 但修改这个地址副本本身(让它指向新对象),不影响外部的原引用
所以本质上还是值传递,只是传递的值是一个内存地址。
调用前: s ──────────────→ [StringBuilder: "Hello"]
调用时: sb(副本)──────→ [StringBuilder: "Hello"] ← 两个变量指向同一对象
修改地址:sb = new ... → [新对象] s 还是指向原来的对象
总结
Java 传引用类型时,传的是"引用的副本",可以通过副本修改对象内容,但不能改变原引用的指向。
🎯 面试答题技巧
- 结论先说:先给出一句话答案,再展开解释
- 举例说明:抽象概念配代码或场景,显示你真的懂
- 说到坑/注意点:比如Integer缓存池、NullPointerException,体现经验
- 适当延伸:问完可以主动提"这个知识点在XX场景下还有…",展示深度
📚 推荐学习资源
- 《Java 核心技术 卷Ⅰ》—— 基础最扎实的教材
- 《Effective Java》—— 进阶必读
- JavaGuide —— 国内最全的Java面试指南
- LeetCode 热题 Hot 100 —— 算法配合练习
💬 如果这篇文章帮到你了,点个赞再走吧!
后续还会更新 Java 集合、JVM、并发编程等专题,欢迎关注。
有疑问或错误欢迎在评论区指出,一起进步!
更多推荐



所有评论(0)