Java基础八股文 - 应届生秋招必备
本文是《Java面试八股文系列》的第一篇,专为应届生、实习生和秋招准备。内容涵盖Java基础的所有核心知识点,包含详细解析和面试高频考点。
📚 目录
一、Java语言特性
1.1 Java的主要特点
面试高频问题:请说说Java的主要特性?
答案:
Java具有以下主要特性:
- 面向对象:Java是纯面向对象语言,支持封装、继承、多态三大特性
- 平台无关性:通过JVM实现"一次编写,到处运行"(Write Once, Run Anywhere)
- 安全性:提供了安全管理器和字节码验证机制
- 多线程:内置多线程支持,便于开发并发程序
- 健壮性:强类型检查、异常处理机制、自动垃圾回收
- 简单易学:去除了C++中的指针、运算符重载等复杂特性
- 分布式:提供了丰富的网络编程接口
- 高性能:JIT编译器可以动态优化热点代码
1.2 JDK、JRE、JVM的区别
面试高频问题:请解释JDK、JRE、JVM的区别和关系?
答案:
-
JVM(Java Virtual Machine):Java虚拟机,负责执行字节码文件
- 提供了跨平台能力
- 负责内存管理、垃圾回收
- 包含类加载器、执行引擎、运行时数据区等
-
JRE(Java Runtime Environment):Java运行环境
- JRE = JVM + Java核心类库
- 提供了Java程序运行所需的最小环境
- 只能运行Java程序,不能编译
-
JDK(Java Development Kit):Java开发工具包
- JDK = JRE + 开发工具(javac、java、javap等)
- 提供了完整的Java开发环境
- 包含编译器、调试器、文档生成工具等
关系: JDK ⊃ JRE ⊃ JVM
1.3 Java程序执行流程
面试高频问题:Java程序是如何执行的?
答案:
- 编写源代码:编写
.java源文件 - 编译:通过
javac编译器将.java文件编译成.class字节码文件 - 类加载:JVM的类加载器将字节码加载到内存
- 字节码验证:验证字节码的合法性和安全性
- 解释执行/JIT编译:
- 解释器逐行解释执行字节码
- JIT编译器将热点代码编译成本地机器码,提高执行效率
- 垃圾回收:GC自动回收不再使用的对象
1.4 Java是编译型还是解释型语言?
面试高频问题:Java是编译型语言还是解释型语言?
答案:
Java是编译与解释结合的语言:
- 编译阶段:
.java→javac编译→.class字节码 - 解释阶段:
.class→JVM解释执行→ 机器码
优势:
- 字节码实现了平台无关性
- JIT编译器可以优化热点代码,提升性能
- 兼顾了可移植性和执行效率
二、基本数据类型
2.1 八大基本数据类型
面试高频问题:Java有哪些基本数据类型?它们的取值范围是多少?
答案:
| 类型 | 字节数 | 位数 | 取值范围 | 默认值 | 包装类 |
|---|---|---|---|---|---|
| byte | 1 | 8 | -128 ~ 127 | 0 | Byte |
| short | 2 | 16 | -32768 ~ 32767 | 0 | Short |
| int | 4 | 32 | -2³¹ ~ 2³¹-1 | 0 | Integer |
| long | 8 | 64 | -2⁶³ ~ 2⁶³-1 | 0L | Long |
| float | 4 | 32 | 约±3.4E+38 | 0.0f | Float |
| double | 8 | 64 | 约±1.7E+308 | 0.0d | Double |
| char | 2 | 16 | 0 ~ 65535 (Unicode) | ‘\u0000’ | Character |
| boolean | - | - | true / false | false | Boolean |
注意事项:
boolean的大小没有明确规定,取决于JVM实现char采用Unicode编码,可以存储中文字符- 浮点数计算存在精度问题,金融计算应使用
BigDecimal
2.2 基本类型与包装类
面试高频问题:基本类型和包装类有什么区别?什么时候用包装类?
答案:
区别:
-
存储方式:
- 基本类型:存储在栈内存中,直接存储值
- 包装类:存储在堆内存中,是对象引用
-
默认值:
- 基本类型:有默认值(如int为0)
- 包装类:默认值为null
-
使用场景:
- 基本类型:性能更高,适合大量计算
- 包装类:可以使用对象方法,支持泛型,可以为null
包装类的使用场景:
- 集合框架(如
List<Integer>) - 泛型参数
- 需要使用对象方法(如
Integer.parseInt()) - 需要null值表示未赋值状态
2.3 自动装箱与拆箱
面试高频问题:什么是自动装箱和拆箱?有什么性能影响?
答案:
自动装箱(Autoboxing):基本类型自动转换为包装类
Integer i = 10; // 自动装箱,等价于 Integer.valueOf(10)
自动拆箱(Unboxing):包装类自动转换为基本类型
int n = i; // 自动拆箱,等价于 i.intValue()
性能影响:
- 频繁装箱拆箱会创建大量临时对象,增加GC压力
- 建议在循环中使用基本类型,避免自动装箱
- 注意空指针异常:拆箱时如果包装类为null会抛出NPE
经典陷阱:
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之间的对象(享元模式),超出范围会创建新对象。
2.4 float和double的精度问题
面试高频问题:为什么浮点数计算不精确?如何解决?
答案:
原因: 浮点数采用IEEE 754标准,使用二进制表示,某些十进制小数无法精确表示。
System.out.println(0.1 + 0.2); // 输出:0.30000000000000004
解决方案:
- 使用BigDecimal(推荐):
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.add(b); // 精确计算
System.out.println(result); // 输出:0.3
注意: 必须使用String参数的构造方法,不能用double参数。
-
使用整数运算:将金额以分为单位存储,避免小数
-
设置精度:使用
setScale()方法设置小数位数和舍入模式
三、面向对象编程
3.1 面向对象三大特性
面试高频问题:请详细说明面向对象的三大特性?
3.1.1 封装(Encapsulation)
定义: 将对象的属性私有化,通过公共方法访问和修改。
优点:
- 隐藏内部实现细节
- 提高安全性
- 便于维护和修改
实现方式:
public class Student {
private String name; // 私有属性
private int age;
// 公共getter/setter方法
public String getName() {
return name;
}
public void setAge(int age) {
if (age > 0 && age < 150) { // 数据校验
this.age = age;
} else {
throw new IllegalArgumentException("年龄不合法");
}
}
}
3.1.2 继承(Inheritance)
定义: 子类继承父类的属性和方法,实现代码复用。
特点:
- Java只支持单继承(一个类只能继承一个父类)
- 支持多层继承
- 所有类都继承自
Object类 - 使用
extends关键字
示例:
public class Animal {
protected String name;
public void eat() {
System.out.println("动物吃东西");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void bark() {
System.out.println("汪汪汪");
}
}
继承的注意事项:
- 子类不能继承父类的私有成员
- 子类可以重写父类的方法
- 构造方法不能被继承
- 使用
super关键字调用父类方法
3.1.3 多态(Polymorphism)
定义: 同一个行为具有多个不同表现形式或形态。
实现方式:
- 方法重载(Overload):编译时多态
- 方法重写(Override):运行时多态
- 接口实现:运行时多态
多态的三个必要条件:
- 继承
- 重写
- 父类引用指向子类对象
示例:
Animal animal1 = new Dog(); // 向上转型
Animal animal2 = new Cat();
animal1.eat(); // 调用Dog的eat方法
animal2.eat(); // 调用Cat的eat方法
多态的优点:
- 提高代码的扩展性和可维护性
- 降低耦合度
- 提高代码的灵活性
3.2 重载与重写的区别
面试高频问题:重载(Overload)和重写(Override)的区别是什么?
答案:
| 特性 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 定义 | 同一个类中,方法名相同,参数不同 | 子类重新实现父类的方法 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回类型 | 可以不同 | 必须相同(或协变) |
| 访问修饰符 | 可以不同 | 不能更严格 |
| 异常 | 可以不同 | 不能抛出新的或更广的异常 |
| 发生时机 | 编译时 | 运行时 |
| 关键字 | 无 | @Override |
重载示例:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
重写示例:
public class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
3.3 抽象类与接口
面试高频问题:抽象类和接口的区别?什么时候用抽象类,什么时候用接口?
答案:
抽象类(Abstract Class)
特点:
- 使用
abstract关键字修饰 - 不能实例化,只能被继承
- 可以有抽象方法和具体方法
- 可以有构造方法
- 可以有成员变量
- 单继承
示例:
public abstract class Shape {
protected String color; // 成员变量
public Shape(String color) { // 构造方法
this.color = color;
}
// 抽象方法
public abstract double getArea();
// 具体方法
public void display() {
System.out.println("颜色:" + color);
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
接口(Interface)
特点:
- 使用
interface关键字 - 不能实例化
- JDK8之前只能有抽象方法
- JDK8开始可以有默认方法和静态方法
- JDK9开始可以有私有方法
- 成员变量默认是
public static final - 支持多实现
示例:
public interface Flyable {
// 抽象方法(默认public abstract)
void fly();
// 默认方法(JDK8+)
default void glide() {
System.out.println("滑翔");
}
// 静态方法(JDK8+)
static void checkAltitude(int altitude) {
if (altitude < 0) {
throw new IllegalArgumentException("高度不能为负");
}
}
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔");
}
}
区别对比
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 继承/实现 | 单继承 | 多实现 |
| 成员变量 | 任意 | public static final |
| 构造方法 | 有 | 无 |
| 方法类型 | 抽象和具体 | 抽象、默认、静态 |
| 访问修饰符 | 任意 | public |
| 设计理念 | is-a关系 | can-do行为 |
使用建议:
- 抽象类:多个类有共同特征和行为,存在"是一个"的关系
- 接口:多个类有共同能力,存在"能做什么"的关系
四、String类详解
4.1 String的不可变性
面试高频问题:为什么String是不可变的?有什么好处?
答案:
不可变性的实现:
public final class String {
private final char[] value; // JDK8
// private final byte[] value; // JDK9+
// 没有提供修改value的方法
}
不可变的原因:
final修饰类,不能被继承final修饰字符数组,引用不能改变- 没有提供修改内部状态的方法
private修饰,外部无法访问
不可变的好处:
- 线程安全:多线程环境下无需同步
- 支持字符串常量池:提高性能,节省内存
- 安全性:防止被恶意修改(如网络连接、文件路径)
- 可以缓存hash值:用作HashMap的key时性能更好
- 支持字符串字面量:编译期优化
4.2 String、StringBuilder、StringBuffer的区别
面试高频问题:String、StringBuilder、StringBuffer有什么区别?
答案:
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全 | 不安全 | 安全(synchronized) |
| 性能 | 低(频繁修改) | 高 | 中等 |
| 使用场景 | 少量字符串操作 | 单线程大量操作 | 多线程大量操作 |
性能对比:
// 不推荐:每次拼接都创建新对象
String str = "";
for (int i = 0; i < 10000; i++) {
str += i; // 创建大量临时对象
}
// 推荐:单线程使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
常用方法:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 追加
sb.insert(5, ","); // 插入
sb.delete(5, 6); // 删除
sb.reverse(); // 反转
sb.replace(0, 5, "Hi"); // 替换
String str = sb.toString();
4.3 字符串常量池
面试高频问题:什么是字符串常量池?String s = new String(“abc”)创建了几个对象?
答案:
字符串常量池(String Pool):
- 位于堆内存中(JDK7+,之前在方法区)
- 存储字符串字面量
- 避免重复创建相同的字符串对象
- 通过
intern()方法手动入池
创建对象分析:
String s1 = "abc"; // 在常量池中创建
String s2 = "abc"; // 复用常量池中的对象
System.out.println(s1 == s2); // true
String s3 = new String("abc"); // 在堆中创建新对象
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true
String s = new String("abc")创建对象个数:
- 如果常量池中不存在"abc":创建2个对象(1个在常量池,1个在堆)
- 如果常量池中已存在"abc":创建1个对象(仅在堆中创建)
intern()方法:
String s1 = new String("hello");
String s2 = s1.intern(); // 将s1指向常量池中的对象
String s3 = "hello";
System.out.println(s2 == s3); // true
System.out.println(s1 == s3); // false
4.4 String常用方法
面试高频问题:String有哪些常用方法?
答案:
String str = "Hello World";
// 1. 长度和判空
str.length(); // 11
str.isEmpty(); // false
str.isBlank(); // false (JDK11+,判断是否全是空白字符)
// 2. 字符访问
str.charAt(0); // 'H'
str.codePointAt(0); // 72
// 3. 比较
str.equals("hello world"); // false
str.equalsIgnoreCase("hello world"); // true
str.compareTo("Hello"); // 6
str.contentEquals("Hello World"); // true
// 4. 搜索
str.contains("World"); // true
str.startsWith("Hello"); // true
str.endsWith("World"); // true
str.indexOf("o"); // 4
str.lastIndexOf("o"); // 7
// 5. 截取
str.substring(0, 5); // "Hello"
str.substring(6); // "World"
// 6. 替换
str.replace("World", "Java"); // "Hello Java"
str.replaceAll("\\s", ""); // "HelloWorld"
str.replaceFirst("l", "L"); // "HeLlo World"
// 7. 分割
String[] parts = str.split(" "); // ["Hello", "World"]
// 8. 大小写转换
str.toLowerCase(); // "hello world"
str.toUpperCase(); // "HELLO WORLD"
// 9. 去空格
" hello ".trim(); // "hello"
" hello ".strip(); // "hello" (JDK11+,支持Unicode空白)
// 10. 格式化
String.format("姓名:%s,年龄:%d", "张三", 20);
String.valueOf(123); // "123"
// 11. 转换
str.toCharArray(); // char数组
str.getBytes(); // byte数组
五、关键字深入
5.1 final关键字
面试高频问题:final关键字有哪些用法?
答案:
5.1.1 修饰类
public final class String {
// 不能被继承
}
5.1.2 修饰方法
public class Parent {
public final void display() {
// 不能被重写
}
}
5.1.3 修饰变量
public class FinalDemo {
// 1. 基本类型:值不能改变
final int age = 20;
// 2. 引用类型:引用不能改变,但对象内容可以改变
final List<String> list = new ArrayList<>();
// list = new ArrayList<>(); // 编译错误
list.add("hello"); // OK
// 3. 方法参数
public void method(final int param) {
// param = 10; // 编译错误
}
// 4. 局部变量
public void test() {
final String str;
str = "hello"; // 只能赋值一次
// str = "world"; // 编译错误
}
}
final变量的初始化时机:
- 声明时初始化
- 构造代码块中初始化
- 构造方法中初始化
5.2 static关键字
面试高频问题:static关键字的作用?静态变量和实例变量的区别?
答案:
5.2.1 static的作用
public class StaticDemo {
// 1. 静态变量(类变量)
static int count = 0;
// 2. 静态方法
public static void showCount() {
System.out.println(count);
}
// 3. 静态代码块(类加载时执行一次)
static {
System.out.println("静态代码块执行");
count = 100;
}
// 4. 静态内部类
static class InnerClass {
// ...
}
}
5.2.2 静态变量 vs 实例变量
| 特性 | 静态变量 | 实例变量 |
|---|---|---|
| 所属 | 类 | 对象 |
| 内存位置 | 方法区 | 堆内存 |
| 创建时机 | 类加载时 | 对象创建时 |
| 调用方式 | 类名.变量 | 对象.变量 |
| 生命周期 | 类卸载时销毁 | 对象被GC回收时销毁 |
| 共享性 | 所有实例共享 | 每个实例独立 |
示例:
public class Counter {
static int staticCount = 0; // 静态变量
int instanceCount = 0; // 实例变量
public Counter() {
staticCount++;
instanceCount++;
}
}
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
System.out.println(Counter.staticCount); // 3
System.out.println(c3.instanceCount); // 1
5.2.3 静态方法的注意事项
public class StaticMethodDemo {
static int a = 10;
int b = 20;
public static void staticMethod() {
System.out.println(a); // OK
// System.out.println(b); // 错误:不能访问实例变量
// this.b; // 错误:不能使用this
// super.method(); // 错误:不能使用super
}
public void instanceMethod() {
System.out.println(a); // OK
System.out.println(b); // OK
}
}
5.3 this和super关键字
面试高频问题:this和super的区别和使用场景?
答案:
5.3.1 this关键字
用途:
- 引用当前对象
- 调用当前类的构造方法
- 区分成员变量和局部变量
public class Student {
private String name;
private int age;
public Student(String name) {
this(name, 0); // 调用另一个构造方法
}
public Student(String name, int age) {
this.name = name; // 区分成员变量和参数
this.age = age;
}
public Student getThis() {
return this; // 返回当前对象
}
public void setName(String name) {
this.name = name;
}
}
5.3.2 super关键字
用途:
- 访问父类的成员变量
- 调用父类的方法
- 调用父类的构造方法
public class Animal {
String name = "动物";
public void eat() {
System.out.println("吃东西");
}
public Animal(String name) {
this.name = name;
}
}
public class Dog extends Animal {
String name = "狗";
public Dog(String name) {
super(name); // 调用父类构造方法(必须是第一句)
}
@Override
public void eat() {
super.eat(); // 调用父类方法
System.out.println("狗吃骨头");
}
public void printName() {
System.out.println(name); // 狗
System.out.println(this.name); // 狗
System.out.println(super.name); // 动物
}
}
this vs super:
| 特性 | this | super |
|---|---|---|
| 含义 | 当前对象 | 父类对象 |
| 访问成员 | 当前类 | 父类 |
| 调用构造 | 当前类其他构造 | 父类构造 |
| 位置要求 | 构造方法第一句 | 构造方法第一句 |
5.4 instanceof关键字
面试高频问题:instanceof的作用是什么?
答案:
instanceof用于判断对象是否是某个类的实例,或者是否实现了某个接口。
public class InstanceofDemo {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println(animal instanceof Dog); // true
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Object); // true
// 避免ClassCastException
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 安全的向下转型
dog.bark();
}
// JDK14+ 模式匹配
if (animal instanceof Dog dog) {
dog.bark(); // 自动转型
}
}
}
注意事项:
- 如果对象为null,结果为false
- 编译时会检查类型是否兼容
- 用于向下转型前的类型检查
六、异常处理
6.1 异常体系结构
面试高频问题:Java异常体系是怎样的?Error和Exception的区别?

Throwable
├── Error (错误,程序无法处理)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (异常,程序可以处理)
├── RuntimeException (运行时异常,非受检异常)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ ├── ClassCastException
│ ├── ArithmeticException
│ └── IllegalArgumentException
└── 受检异常 (Checked Exception)
├── IOException
├── SQLException
├── FileNotFoundException
└── ClassNotFoundException
Error vs Exception:
| 特性 | Error | Exception |
|---|---|---|
| 定义 | 系统级错误 | 程序级异常 |
| 是否可恢复 | 不可恢复 | 可恢复 |
| 处理方式 | 无法处理 | 应该捕获处理 |
| 示例 | OOM、栈溢出 | 空指针、IO异常 |
6.2 受检异常与非受检异常
面试高频问题:什么是受检异常和非受检异常?区别是什么?
答案:
受检异常(Checked Exception)
- 编译时必须处理(try-catch或throws)
- 继承自
Exception但不是RuntimeException - 代表预期可能发生的异常
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // 必须处理IOException
// ...
}
// 或者
public void readFile(String path) {
try {
FileReader reader = new FileReader(path);
// ...
} catch (IOException e) {
e.printStackTrace();
}
}
非受检异常(Unchecked Exception)
- 编译时不要求处理
- 包括
RuntimeException及其子类 - 通常是程序逻辑错误
public void divide(int a, int b) {
int result = a / b; // 可能抛出ArithmeticException,不强制处理
}
区别对比:
| 特性 | 受检异常 | 非受检异常 |
|---|---|---|
| 检查时机 | 编译时 | 运行时 |
| 是否强制处理 | 是 | 否 |
| 继承关系 | Exception(非Runtime) | RuntimeException |
| 产生原因 | 外部因素 | 程序逻辑错误 |
| 示例 | IOException、SQLException | NPE、数组越界 |
6.3 异常处理机制
面试高频问题:Java异常处理的方式有哪些?try-catch-finally的执行顺序?
答案:
6.3.1 try-catch-finally
public void method() {
try {
// 可能抛出异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 处理特定异常
System.out.println("算术异常:" + e.getMessage());
} catch (Exception e) {
// 处理其他异常
System.out.println("其他异常:" + e.getMessage());
} finally {
// 无论是否发生异常都会执行
System.out.println("finally块执行");
}
}
6.3.2 执行顺序
场景1:无异常
try → finally → 后续代码
场景2:有异常且被catch
try → catch → finally → 后续代码
场景3:有异常但未被catch
try → finally → 方法终止(异常向上抛出)
6.3.3 finally的特殊情况
面试高频问题:finally一定会执行吗?什么情况下不执行?
答案:
以下情况finally不会执行:
- JVM退出:
System.exit(0) - 所在线程死亡
- CPU关闭
返回值陷阱:
public int test() {
int x = 1;
try {
x = 2;
return x; // 返回前会执行finally
} finally {
x = 3; // 不会影响返回值
// return x; // 如果finally有return,会覆盖try的return
}
}
// 返回值为2,不是3
原理: return执行前会将返回值保存到局部变量表,finally中修改变量不影响已保存的返回值。
6.3.4 try-with-resources
JDK7+自动资源管理:
// 传统方式
public void readFile() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// try-with-resources(推荐)
public void readFile() {
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
// 自动关闭资源
}
要求: 资源必须实现AutoCloseable接口。
6.4 自定义异常
面试高频问题:如何自定义异常?
答案:
// 自定义受检异常
public class BusinessException extends Exception {
private int errorCode;
public BusinessException(String message) {
super(message);
}
public BusinessException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public int getErrorCode() {
return errorCode;
}
}
// 自定义非受检异常
public class ServiceException extends RuntimeException {
public ServiceException(String message) {
super(message);
}
}
// 使用
public void transfer(Account from, Account to, BigDecimal amount)
throws BusinessException {
if (from.getBalance().compareTo(amount) < 0) {
throw new BusinessException(1001, "余额不足");
}
// ...
}
6.5 异常处理最佳实践
面试高频问题:异常处理有哪些最佳实践?
答案:
- 不要捕获后什么都不做
// 错误示范
try {
// ...
} catch (Exception e) {
// 空catch,吞掉异常
}
// 正确做法
try {
// ...
} catch (Exception e) {
logger.error("操作失败", e); // 至少记录日志
throw new ServiceException("操作失败", e);
}
- 不要捕获Throwable或Error
// 不推荐
try {
// ...
} catch (Throwable t) { // 会捕获Error
// ...
}
- 具体异常优先捕获
try {
// ...
} catch (FileNotFoundException e) {
// 先捕获具体异常
} catch (IOException e) {
// 再捕获父类异常
}
-
在finally中清理资源或使用try-with-resources
-
不要使用异常控制流程
// 错误示范
try {
int i = 0;
while (true) {
array[i++] = i;
}
} catch (ArrayIndexOutOfBoundsException e) {
// 不要用异常控制循环
}
- 添加异常信息
throw new BusinessException("用户注册失败:邮箱已存在 [email=" + email + "]");
七、集合框架基础
7.1 集合框架概述
面试高频问题:Java集合框架的层次结构是怎样的?
答案:
Collection (接口)
├── List (有序,可重复)
│ ├── ArrayList (动态数组)
│ ├── LinkedList (双向链表)
│ └── Vector (线程安全,已过时)
│ └── Stack
├── Set (无序,不重复)
│ ├── HashSet (哈希表)
│ │ └── LinkedHashSet (保持插入顺序)
│ └── TreeSet (红黑树,有序)
└── Queue (队列)
├── PriorityQueue (优先队列)
└── Deque (双端队列)
└── ArrayDeque
Map (接口,键值对)
├── HashMap (哈希表)
│ └── LinkedHashMap (保持插入顺序)
├── TreeMap (红黑树,有序)
├── Hashtable (线程安全,已过时)
│ └── Properties
└── ConcurrentHashMap (线程安全)
7.2 List接口
面试高频问题:ArrayList和LinkedList的区别?
答案:
ArrayList vs LinkedList
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层实现 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 插入/删除(末尾) | O(1) | O(1) |
| 插入/删除(中间) | O(n) | O(1) |
| 内存占用 | 连续内存 | 额外指针开销 |
| 适用场景 | 频繁查询 | 频繁增删 |
示例:
// ArrayList
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add(0, "B"); // 中间插入,需要移动元素
String item = arrayList.get(0); // 快速访问
// LinkedList
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add(0, "B"); // 中间插入,只需修改指针
((LinkedList<String>) linkedList).addFirst("C"); // 支持队列操作
7.3 Set接口
面试高频问题:HashSet、LinkedHashSet、TreeSet的区别?
答案:
| 特性 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底层实现 | HashMap | LinkedHashMap | TreeMap |
| 是否有序 | 无序 | 插入顺序 | 自然排序/比较器 |
| 是否允许null | 允许一个 | 允许一个 | 不允许 |
| 性能 | O(1) | O(1) | O(log n) |
示例:
// HashSet:无序
Set<String> hashSet = new HashSet<>();
hashSet.add("C");
hashSet.add("A");
hashSet.add("B");
System.out.println(hashSet); // [A, B, C] 或其他顺序
// LinkedHashSet:保持插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("C");
linkedHashSet.add("A");
linkedHashSet.add("B");
System.out.println(linkedHashSet); // [C, A, B]
// TreeSet:自然排序
Set<String> treeSet = new TreeSet<>();
treeSet.add("C");
treeSet.add("A");
treeSet.add("B");
System.out.println(treeSet); // [A, B, C]
7.4 Map接口
面试高频问题:HashMap、Hashtable、ConcurrentHashMap的区别?
答案:
| 特性 | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | 否 | 是 | 是 |
| null键值 | 允许 | 不允许 | 不允许 |
| 性能 | 高 | 低(全局锁) | 高(分段锁) |
| 继承 | AbstractMap | Dictionary | AbstractMap |
| 推荐使用 | 单线程 | 不推荐 | 多线程 |
HashMap示例:
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 遍历方式
// 1. entrySet(推荐)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 2. keySet
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
// 3. values
for (Integer value : map.values()) {
System.out.println(value);
}
// 4. forEach (JDK8+)
map.forEach((k, v) -> System.out.println(k + "=" + v));
7.5 集合工具类
面试高频问题:Collections和Arrays有哪些常用方法?
答案:
// Collections工具类
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
Collections.sort(list); // 排序
Collections.reverse(list); // 反转
Collections.shuffle(list); // 随机排列
Collections.max(list); // 最大值
Collections.min(list); // 最小值
Collections.frequency(list, 1); // 元素出现次数
Collections.binarySearch(list, 5); // 二分查找
// 线程安全包装
List<Integer> syncList = Collections.synchronizedList(list);
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 不可变集合
List<String> immutableList = Collections.unmodifiableList(list);
// Arrays工具类
int[] arr = {3, 1, 4, 1, 5, 9};
Arrays.sort(arr); // 排序
Arrays.binarySearch(arr, 5); // 二分查找
Arrays.fill(arr, 0); // 填充
Arrays.equals(arr, arr2); // 比较
String str = Arrays.toString(arr); // 转字符串
List<Integer> list2 = Arrays.asList(1, 2, 3); // 转List
八、IO流基础
8.1 IO流分类
面试高频问题:Java IO流的分类?字节流和字符流的区别?
答案:
按流向分类:
- 输入流:读取数据(InputStream、Reader)
- 输出流:写入数据(OutputStream、Writer)
按数据类型分类:
- 字节流:处理二进制数据(InputStream、OutputStream)
- 字符流:处理文本数据(Reader、Writer)
字节流 vs 字符流:
| 特性 | 字节流 | 字符流 |
|---|---|---|
| 处理单位 | 字节(byte) | 字符(char) |
| 适用场景 | 所有类型文件 | 文本文件 |
| 编码处理 | 不处理 | 自动处理 |
| 基类 | InputStream/OutputStream | Reader/Writer |
8.2 常用IO流
面试高频问题:常用的IO流有哪些?
答案:
8.2.1 文件流
// 字节流读写文件
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
}
// 字符流读写文件
try (FileReader fr = new FileReader("input.txt");
FileWriter fw = new FileWriter("output.txt")) {
int data;
while ((data = fr.read()) != -1) {
fw.write(data);
}
}
8.2.2 缓冲流
// 缓冲字节流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
}
// 缓冲字符流(常用)
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = br.readLine()) != null) { // 按行读取
bw.write(line);
bw.newLine(); // 换行
}
}
8.2.3 转换流
// InputStreamReader:字节流转字符流
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("file.txt"), "UTF-8");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// OutputStreamWriter:字符流转字节流
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("file.txt"), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw)) {
bw.write("Hello World");
}
8.2.4 对象流(序列化)
// 对象输出流
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("object.dat"))) {
oos.writeObject(new Student("张三", 20));
}
// 对象输入流
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("object.dat"))) {
Student student = (Student) ois.readObject();
}
8.3 NIO
面试高频问题:NIO和传统IO的区别?
答案:
| 特性 | 传统IO | NIO |
|---|---|---|
| 面向 | 流 | 缓冲区 |
| 阻塞方式 | 阻塞IO | 非阻塞IO |
| 选择器 | 无 | 有 |
| 性能 | 低 | 高 |
NIO核心组件:
- Channel(通道):双向读写
- Buffer(缓冲区):数据容器
- Selector(选择器):多路复用
// NIO读取文件
try (FileChannel channel = FileChannel.open(Paths.get("file.txt"),
StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip(); // 切换到读模式
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // 清空缓冲区
}
}
九、反射机制
9.1 什么是反射
面试高频问题:什么是反射?反射的应用场景?
答案:
反射(Reflection) 是Java的一种动态机制,允许程序在运行时获取类的信息并操作类的成员(字段、方法、构造器)。
核心类:
Class:类的字节码对象Constructor:构造器Field:字段Method:方法
应用场景:
- 框架开发:Spring的IOC、AOP
- 动态代理:MyBatis的Mapper接口
- 注解处理:JUnit的测试用例扫描
- 序列化:JSON转换(Jackson、Gson)
- JDBC:数据库驱动加载
9.2 获取Class对象
面试高频问题:获取Class对象的方式有哪些?
答案:
// 方式1:通过类名.class
Class<String> clazz1 = String.class;
// 方式2:通过对象.getClass()
String str = "hello";
Class<?> clazz2 = str.getClass();
// 方式3:通过Class.forName()(常用)
Class<?> clazz3 = Class.forName("java.lang.String");
// 判断是否是同一个Class对象
System.out.println(clazz1 == clazz2); // true
9.3 反射常用操作
面试高频问题:如何通过反射创建对象、调用方法、访问字段?
答案:
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
private void study() {
System.out.println(name + "在学习");
}
public void show(String message) {
System.out.println(message);
}
}
// 反射操作
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 获取Class对象
Class<?> clazz = Class.forName("com.example.Student");
// 2. 创建对象
// 方式1:无参构造
Object obj1 = clazz.newInstance(); // JDK9+已过时
Object obj2 = clazz.getDeclaredConstructor().newInstance();
// 方式2:有参构造
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj3 = constructor.newInstance("张三", 20);
// 3. 访问字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 访问私有字段
nameField.set(obj3, "李四"); // 设置值
String name = (String) nameField.get(obj3); // 获取值
// 4. 调用方法
// 公共方法
Method showMethod = clazz.getMethod("show", String.class);
showMethod.invoke(obj3, "Hello");
// 私有方法
Method studyMethod = clazz.getDeclaredMethod("study");
studyMethod.setAccessible(true);
studyMethod.invoke(obj3);
// 5. 获取类信息
System.out.println("类名:" + clazz.getName());
System.out.println("简单类名:" + clazz.getSimpleName());
System.out.println("包名:" + clazz.getPackage().getName());
System.out.println("父类:" + clazz.getSuperclass());
System.out.println("接口:" + Arrays.toString(clazz.getInterfaces()));
// 6. 获取所有成员
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
}
}
9.4 反射的优缺点
面试高频问题:反射有什么优缺点?
答案:
优点:
- 灵活性:运行时动态操作类
- 扩展性:便于实现框架和插件机制
- 解耦:降低代码耦合度
缺点:
- 性能开销:比直接调用慢10-100倍
- 安全问题:破坏封装性,可以访问私有成员
- 代码复杂:增加代码维护难度
- 编译检查:无法在编译期发现错误
优化建议:
- 缓存Class对象、Method对象
- 使用
setAccessible(true)跳过安全检查 - 避免在循环中使用反射
- 考虑使用MethodHandle(JDK7+)
十、注解
10.1 什么是注解
面试高频问题:什么是注解?注解的作用?
答案:
注解(Annotation) 是JDK5引入的一种元数据,为代码提供额外信息,不直接影响代码逻辑。
作用:
- 编译检查:@Override、@Deprecated
- 代码生成:Lombok的@Data、@Getter
- 运行时处理:Spring的@Autowired、@RequestMapping
- 文档生成:@param、@return
10.2 内置注解
面试高频问题:Java有哪些内置注解?
答案:
// 1. @Override:检查方法是否重写父类方法
@Override
public String toString() {
return "Student";
}
// 2. @Deprecated:标记已过时的元素
@Deprecated
public void oldMethod() {
// ...
}
// 3. @SuppressWarnings:抑制编译警告
@SuppressWarnings("unchecked")
List list = new ArrayList();
// 4. @FunctionalInterface:标记函数式接口(JDK8+)
@FunctionalInterface
interface MyInterface {
void method();
}
// 5. @SafeVarargs:抑制可变参数警告(JDK7+)
@SafeVarargs
public final <T> void method(T... args) {
// ...
}
10.3 元注解
面试高频问题:什么是元注解?有哪些元注解?
答案:
元注解 是用于修饰注解的注解。
// 1. @Target:指定注解的使用位置
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
}
// ElementType枚举值:
// - TYPE:类、接口、枚举
// - FIELD:字段
// - METHOD:方法
// - PARAMETER:参数
// - CONSTRUCTOR:构造器
// - LOCAL_VARIABLE:局部变量
// - ANNOTATION_TYPE:注解
// - PACKAGE:包
// 2. @Retention:指定注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
// RetentionPolicy枚举值:
// - SOURCE:源码期,编译后丢弃
// - CLASS:编译期,保留到class文件(默认)
// - RUNTIME:运行期,可通过反射读取
// 3. @Documented:是否包含在JavaDoc中
@Documented
public @interface MyAnnotation {
}
// 4. @Inherited:是否可被子类继承
@Inherited
public @interface MyAnnotation {
}
// 5. @Repeatable:是否可重复使用(JDK8+)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
@interface MyAnnotations {
MyAnnotation[] value();
}
10.4 自定义注解
面试高频问题:如何自定义注解并使用?
答案:
// 1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default ""; // 默认值
int level() default 1;
}
// 2. 使用注解
public class UserService {
@Log(value = "查询用户", level = 2)
public User getUser(Long id) {
// ...
return null;
}
}
// 3. 解析注解
public class AnnotationProcessor {
public static void process(Class<?> clazz) throws Exception {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 判断方法是否有Log注解
if (method.isAnnotationPresent(Log.class)) {
// 获取注解
Log log = method.getAnnotation(Log.class);
System.out.println("方法:" + method.getName());
System.out.println("描述:" + log.value());
System.out.println("级别:" + log.level());
}
}
}
public static void main(String[] args) throws Exception {
process(UserService.class);
}
}
十一、泛型
11.1 什么是泛型
面试高频问题:什么是泛型?泛型的作用?
答案:
泛型(Generics) 是JDK5引入的特性,允许在定义类、接口、方法时使用类型参数。
作用:
- 类型安全:编译期检查类型
- 消除类型转换:避免强制类型转换
- 代码复用:编写通用算法
示例:
// 没有泛型
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 需要强转
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0); // 无需强转
// list.add(123); // 编译错误
11.2 泛型类和泛型接口
面试高频问题:如何定义泛型类和泛型接口?
答案:
// 泛型类
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用
Box<String> box1 = new Box<>();
box1.set("hello");
String str = box1.get();
Box<Integer> box2 = new Box<>();
box2.set(123);
Integer num = box2.get();
// 泛型接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
// 实现泛型接口
public class StringComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}
// 多个类型参数
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
11.3 泛型方法
面试高频问题:如何定义和使用泛型方法?
答案:
public class GenericMethod {
// 泛型方法:在返回值前声明类型参数
public static <T> T getMiddle(T... args) {
return args[args.length / 2];
}
// 多个类型参数
public static <K, V> void printPair(K key, V value) {
System.out.println(key + "=" + value);
}
// 使用
public static void main(String[] args) {
String middle = getMiddle("a", "b", "c"); // 自动推断类型
Integer num = GenericMethod.<Integer>getMiddle(1, 2, 3); // 显式指定
printPair("name", "张三");
printPair("age", 20);
}
}
11.4 泛型通配符
面试高频问题:泛型通配符有哪些?有什么区别?
答案:
11.4.1 无界通配符(?)
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 可以接受任意类型的List
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());
11.4.2 上界通配符(? extends T)
// 只能读取,不能写入(除了null)
public double sumOfList(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 可以接受Number及其子类的List
sumOfList(new ArrayList<Integer>());
sumOfList(new ArrayList<Double>());
11.4.3 下界通配符(? super T)
// 只能写入,读取时只能用Object接收
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
// Integer num = list.get(0); // 编译错误
Object obj = list.get(0); // OK
}
// 可以接受Integer及其父类的List
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());
PECS原则:
- Producer Extends:如果只读取,使用
? extends T - Consumer Super:如果只写入,使用
? super T
11.5 类型擦除
面试高频问题:什么是类型擦除?泛型的局限性?
答案:
类型擦除 是Java泛型的实现机制,在编译后擦除泛型信息,转换为原始类型。
// 编译前
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 编译后(类型擦除)
List list1 = new ArrayList();
List list2 = new ArrayList();
// 运行时类型相同
System.out.println(list1.getClass() == list2.getClass()); // true
泛型的局限性:
- 不能实例化类型参数
// 错误
T obj = new T();
T[] array = new T[10];
- 不能使用基本类型
// 错误
List<int> list = new ArrayList<>();
// 正确:使用包装类
List<Integer> list = new ArrayList<>();
- 不能创建泛型数组
// 错误
List<String>[] arrays = new List<String>[10];
// 正确:使用通配符
List<?>[] arrays = new List<?>[10];
- 静态成员不能使用泛型
public class Box<T> {
// 错误
private static T value;
// 错误
public static T getValue() {
return value;
}
}
十二、序列化
12.1 什么是序列化
面试高频问题:什么是序列化和反序列化?有什么用?
答案:
序列化(Serialization):将对象转换为字节流,便于存储或传输
反序列化(Deserialization):将字节流还原为对象
用途:
- 持久化:保存对象状态到文件或数据库
- 网络传输:通过网络发送对象
- 进程通信:在不同进程间传递对象
- 深拷贝:通过序列化实现对象深拷贝
12.2 实现序列化
面试高频问题:如何实现序列化?
答案:
// 1. 实现Serializable接口
public class Student implements Serializable {
// 序列化版本号(强烈推荐)
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient修饰的字段不会被序列化
private transient String password;
// static字段不会被序列化
private static String school = "清华大学";
public Student(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
// getter/setter...
}
// 2. 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("student.dat"))) {
Student student = new Student("张三", 20, "123456");
oos.writeObject(student);
}
// 3. 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("student.dat"))) {
Student student = (Student) ois.readObject();
System.out.println(student.getName()); // 张三
System.out.println(student.getAge()); // 20
System.out.println(student.getPassword()); // null(transient)
}
12.3 serialVersionUID
面试高频问题:serialVersionUID的作用是什么?
答案:
serialVersionUID是序列化版本号,用于验证序列化对象的发送方和接收方是否兼容。
作用:
- 版本控制:确保类的版本一致
- 兼容性:判断序列化对象是否可以反序列化
不指定serialVersionUID的问题:
- JVM会自动生成,基于类的结构(字段、方法)
- 类结构改变后,版本号会变化
- 导致反序列化失败:
InvalidClassException
最佳实践:
// 显式声明serialVersionUID
private static final long serialVersionUID = 1L;
12.4 自定义序列化
面试高频问题:如何自定义序列化过程?
答案:
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String password;
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream oos) throws IOException {
// 先写入默认字段
oos.defaultWriteObject();
// 加密密码后写入
String encryptedPassword = encrypt(password);
oos.writeObject(encryptedPassword);
}
// 自定义反序列化逻辑
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
// 先读取默认字段
ois.defaultReadObject();
// 读取并解密密码
String encryptedPassword = (String) ois.readObject();
this.password = decrypt(encryptedPassword);
}
private String encrypt(String str) {
// 加密逻辑
return str;
}
private String decrypt(String str) {
// 解密逻辑
return str;
}
}
12.5 Externalizable接口
面试高频问题:Serializable和Externalizable的区别?
答案:
| 特性 | Serializable | Externalizable |
|---|---|---|
| 实现难度 | 简单(自动) | 复杂(手动) |
| 性能 | 较慢 | 较快 |
| 控制粒度 | 粗 | 细 |
| 默认行为 | 自动序列化所有非transient字段 | 需要手动实现 |
public class Student implements Externalizable {
private String name;
private int age;
// 必须有无参构造方法
public Student() {}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 手动写入字段
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
// 手动读取字段(顺序必须一致)
this.name = (String) in.readObject();
this.age = in.readInt();
}
}
总结
高频面试题清单
必背题(⭐⭐⭐)
- Java的三大特性(封装、继承、多态)
- JDK、JRE、JVM的区别
- 八大基本数据类型及取值范围
- String、StringBuilder、StringBuffer的区别
- String为什么不可变?
- ==和equals的区别
- 重载和重写的区别
- 抽象类和接口的区别
- ArrayList和LinkedList的区别
- HashMap的底层原理(JDK8)
- final关键字的作用
- static关键字的作用
- 异常体系结构
- try-catch-finally执行顺序
- 反射的概念和应用
重要题(⭐⭐)
- Java是编译型还是解释型语言?
- 自动装箱和拆箱
- Integer缓存池
- float和double精度问题
- this和super的区别
- instanceof的作用
- 受检异常和非受检异常
- HashSet、LinkedHashSet、TreeSet的区别
- HashMap、Hashtable、ConcurrentHashMap的区别
- 字节流和字符流的区别
- BIO、NIO、AIO的区别
- 泛型的作用和类型擦除
- 序列化和反序列化
- serialVersionUID的作用
下期预告
下一篇将深入讲解《Java集合框架》,包括:
- HashMap底层原理(数组+链表+红黑树)
- ConcurrentHashMap的实现机制
- ArrayList扩容机制
- LinkedList的实现细节
- 常见集合类的源码分析
51万+

被折叠的 条评论
为什么被折叠?



