Java基础

Java基础八股文 - 应届生秋招必备

本文是《Java面试八股文系列》的第一篇,专为应届生、实习生和秋招准备。内容涵盖Java基础的所有核心知识点,包含详细解析和面试高频考点。

📚 目录


一、Java语言特性

1.1 Java的主要特点

面试高频问题:请说说Java的主要特性?

答案:

Java具有以下主要特性:

  1. 面向对象:Java是纯面向对象语言,支持封装、继承、多态三大特性
  2. 平台无关性:通过JVM实现"一次编写,到处运行"(Write Once, Run Anywhere)
  3. 安全性:提供了安全管理器和字节码验证机制
  4. 多线程:内置多线程支持,便于开发并发程序
  5. 健壮性:强类型检查、异常处理机制、自动垃圾回收
  6. 简单易学:去除了C++中的指针、运算符重载等复杂特性
  7. 分布式:提供了丰富的网络编程接口
  8. 高性能: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程序是如何执行的?

答案:

  1. 编写源代码:编写.java源文件
  2. 编译:通过javac编译器将.java文件编译成.class字节码文件
  3. 类加载:JVM的类加载器将字节码加载到内存
  4. 字节码验证:验证字节码的合法性和安全性
  5. 解释执行/JIT编译
    • 解释器逐行解释执行字节码
    • JIT编译器将热点代码编译成本地机器码,提高执行效率
  6. 垃圾回收:GC自动回收不再使用的对象

1.4 Java是编译型还是解释型语言?

面试高频问题:Java是编译型语言还是解释型语言?

答案:

Java是编译与解释结合的语言:

  1. 编译阶段.javajavac编译.class字节码
  2. 解释阶段.classJVM解释执行 → 机器码

优势:

  • 字节码实现了平台无关性
  • JIT编译器可以优化热点代码,提升性能
  • 兼顾了可移植性和执行效率

二、基本数据类型

2.1 八大基本数据类型

面试高频问题:Java有哪些基本数据类型?它们的取值范围是多少?

答案:

类型字节数位数取值范围默认值包装类
byte18-128 ~ 1270Byte
short216-32768 ~ 327670Short
int432-2³¹ ~ 2³¹-10Integer
long864-2⁶³ ~ 2⁶³-10LLong
float432约±3.4E+380.0fFloat
double864约±1.7E+3080.0dDouble
char2160 ~ 65535 (Unicode)‘\u0000’Character
boolean--true / falsefalseBoolean

注意事项:

  1. boolean的大小没有明确规定,取决于JVM实现
  2. char采用Unicode编码,可以存储中文字符
  3. 浮点数计算存在精度问题,金融计算应使用BigDecimal

2.2 基本类型与包装类

面试高频问题:基本类型和包装类有什么区别?什么时候用包装类?

答案:

区别:

  1. 存储方式

    • 基本类型:存储在栈内存中,直接存储值
    • 包装类:存储在堆内存中,是对象引用
  2. 默认值

    • 基本类型:有默认值(如int为0)
    • 包装类:默认值为null
  3. 使用场景

    • 基本类型:性能更高,适合大量计算
    • 包装类:可以使用对象方法,支持泛型,可以为null

包装类的使用场景:

  • 集合框架(如List<Integer>
  • 泛型参数
  • 需要使用对象方法(如Integer.parseInt()
  • 需要null值表示未赋值状态

2.3 自动装箱与拆箱

面试高频问题:什么是自动装箱和拆箱?有什么性能影响?

答案:

自动装箱(Autoboxing):基本类型自动转换为包装类

Integer i = 10;  // 自动装箱,等价于 Integer.valueOf(10)

自动拆箱(Unboxing):包装类自动转换为基本类型

int n = i;  // 自动拆箱,等价于 i.intValue()

性能影响:

  1. 频繁装箱拆箱会创建大量临时对象,增加GC压力
  2. 建议在循环中使用基本类型,避免自动装箱
  3. 注意空指针异常:拆箱时如果包装类为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

解决方案:

  1. 使用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参数。

  1. 使用整数运算:将金额以分为单位存储,避免小数

  2. 设置精度:使用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("汪汪汪");
    }
}

继承的注意事项:

  1. 子类不能继承父类的私有成员
  2. 子类可以重写父类的方法
  3. 构造方法不能被继承
  4. 使用super关键字调用父类方法
3.1.3 多态(Polymorphism)

定义: 同一个行为具有多个不同表现形式或形态。

实现方式:

  1. 方法重载(Overload):编译时多态
  2. 方法重写(Override):运行时多态
  3. 接口实现:运行时多态

多态的三个必要条件:

  1. 继承
  2. 重写
  3. 父类引用指向子类对象

示例:

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的方法
}

不可变的原因:

  1. final修饰类,不能被继承
  2. final修饰字符数组,引用不能改变
  3. 没有提供修改内部状态的方法
  4. private修饰,外部无法访问

不可变的好处:

  1. 线程安全:多线程环境下无需同步
  2. 支持字符串常量池:提高性能,节省内存
  3. 安全性:防止被恶意修改(如网络连接、文件路径)
  4. 可以缓存hash值:用作HashMap的key时性能更好
  5. 支持字符串字面量:编译期优化

4.2 String、StringBuilder、StringBuffer的区别

面试高频问题:String、StringBuilder、StringBuffer有什么区别?

答案:

特性StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全安全不安全安全(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变量的初始化时机:

  1. 声明时初始化
  2. 构造代码块中初始化
  3. 构造方法中初始化

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关键字

用途:

  1. 引用当前对象
  2. 调用当前类的构造方法
  3. 区分成员变量和局部变量
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关键字

用途:

  1. 访问父类的成员变量
  2. 调用父类的方法
  3. 调用父类的构造方法
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:

特性thissuper
含义当前对象父类对象
访问成员当前类父类
调用构造当前类其他构造父类构造
位置要求构造方法第一句构造方法第一句

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();  // 自动转型
        }
    }
}

注意事项:

  1. 如果对象为null,结果为false
  2. 编译时会检查类型是否兼容
  3. 用于向下转型前的类型检查

六、异常处理

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:

特性ErrorException
定义系统级错误程序级异常
是否可恢复不可恢复可恢复
处理方式无法处理应该捕获处理
示例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、SQLExceptionNPE、数组越界

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不会执行:

  1. JVM退出:System.exit(0)
  2. 所在线程死亡
  3. 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 异常处理最佳实践

面试高频问题:异常处理有哪些最佳实践?

答案:

  1. 不要捕获后什么都不做
// 错误示范
try {
    // ...
} catch (Exception e) {
    // 空catch,吞掉异常
}

// 正确做法
try {
    // ...
} catch (Exception e) {
    logger.error("操作失败", e);  // 至少记录日志
    throw new ServiceException("操作失败", e);
}
  1. 不要捕获Throwable或Error
// 不推荐
try {
    // ...
} catch (Throwable t) {  // 会捕获Error
    // ...
}
  1. 具体异常优先捕获
try {
    // ...
} catch (FileNotFoundException e) {
    // 先捕获具体异常
} catch (IOException e) {
    // 再捕获父类异常
}
  1. 在finally中清理资源或使用try-with-resources

  2. 不要使用异常控制流程

// 错误示范
try {
    int i = 0;
    while (true) {
        array[i++] = i;
    }
} catch (ArrayIndexOutOfBoundsException e) {
    // 不要用异常控制循环
}
  1. 添加异常信息
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
特性ArrayListLinkedList
底层实现动态数组双向链表
随机访问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的区别?

答案:

特性HashSetLinkedHashSetTreeSet
底层实现HashMapLinkedHashMapTreeMap
是否有序无序插入顺序自然排序/比较器
是否允许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的区别?

答案:

特性HashMapHashtableConcurrentHashMap
线程安全
null键值允许不允许不允许
性能低(全局锁)高(分段锁)
继承AbstractMapDictionaryAbstractMap
推荐使用单线程不推荐多线程

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/OutputStreamReader/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的区别?

答案:

特性传统IONIO
面向缓冲区
阻塞方式阻塞IO非阻塞IO
选择器
性能

NIO核心组件:

  1. Channel(通道):双向读写
  2. Buffer(缓冲区):数据容器
  3. 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:方法

应用场景:

  1. 框架开发:Spring的IOC、AOP
  2. 动态代理:MyBatis的Mapper接口
  3. 注解处理:JUnit的测试用例扫描
  4. 序列化:JSON转换(Jackson、Gson)
  5. 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 反射的优缺点

面试高频问题:反射有什么优缺点?

答案:

优点:

  1. 灵活性:运行时动态操作类
  2. 扩展性:便于实现框架和插件机制
  3. 解耦:降低代码耦合度

缺点:

  1. 性能开销:比直接调用慢10-100倍
  2. 安全问题:破坏封装性,可以访问私有成员
  3. 代码复杂:增加代码维护难度
  4. 编译检查:无法在编译期发现错误

优化建议:

  1. 缓存Class对象、Method对象
  2. 使用setAccessible(true)跳过安全检查
  3. 避免在循环中使用反射
  4. 考虑使用MethodHandle(JDK7+)

十、注解

10.1 什么是注解

面试高频问题:什么是注解?注解的作用?

答案:

注解(Annotation) 是JDK5引入的一种元数据,为代码提供额外信息,不直接影响代码逻辑。

作用:

  1. 编译检查:@Override、@Deprecated
  2. 代码生成:Lombok的@Data、@Getter
  3. 运行时处理:Spring的@Autowired、@RequestMapping
  4. 文档生成:@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引入的特性,允许在定义类、接口、方法时使用类型参数。

作用:

  1. 类型安全:编译期检查类型
  2. 消除类型转换:避免强制类型转换
  3. 代码复用:编写通用算法

示例:

// 没有泛型
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

泛型的局限性:

  1. 不能实例化类型参数
// 错误
T obj = new T();
T[] array = new T[10];
  1. 不能使用基本类型
// 错误
List<int> list = new ArrayList<>();

// 正确:使用包装类
List<Integer> list = new ArrayList<>();
  1. 不能创建泛型数组
// 错误
List<String>[] arrays = new List<String>[10];

// 正确:使用通配符
List<?>[] arrays = new List<?>[10];
  1. 静态成员不能使用泛型
public class Box<T> {
    // 错误
    private static T value;
    
    // 错误
    public static T getValue() {
        return value;
    }
}

十二、序列化

12.1 什么是序列化

面试高频问题:什么是序列化和反序列化?有什么用?

答案:

序列化(Serialization):将对象转换为字节流,便于存储或传输
反序列化(Deserialization):将字节流还原为对象

用途:

  1. 持久化:保存对象状态到文件或数据库
  2. 网络传输:通过网络发送对象
  3. 进程通信:在不同进程间传递对象
  4. 深拷贝:通过序列化实现对象深拷贝

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是序列化版本号,用于验证序列化对象的发送方和接收方是否兼容。

作用:

  1. 版本控制:确保类的版本一致
  2. 兼容性:判断序列化对象是否可以反序列化

不指定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的区别?

答案:

特性SerializableExternalizable
实现难度简单(自动)复杂(手动)
性能较慢较快
控制粒度
默认行为自动序列化所有非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();
    }
}

总结

高频面试题清单

必背题(⭐⭐⭐)
  1. Java的三大特性(封装、继承、多态)
  2. JDK、JRE、JVM的区别
  3. 八大基本数据类型及取值范围
  4. String、StringBuilder、StringBuffer的区别
  5. String为什么不可变?
  6. ==和equals的区别
  7. 重载和重写的区别
  8. 抽象类和接口的区别
  9. ArrayList和LinkedList的区别
  10. HashMap的底层原理(JDK8)
  11. final关键字的作用
  12. static关键字的作用
  13. 异常体系结构
  14. try-catch-finally执行顺序
  15. 反射的概念和应用
重要题(⭐⭐)
  1. Java是编译型还是解释型语言?
  2. 自动装箱和拆箱
  3. Integer缓存池
  4. float和double精度问题
  5. this和super的区别
  6. instanceof的作用
  7. 受检异常和非受检异常
  8. HashSet、LinkedHashSet、TreeSet的区别
  9. HashMap、Hashtable、ConcurrentHashMap的区别
  10. 字节流和字符流的区别
  11. BIO、NIO、AIO的区别
  12. 泛型的作用和类型擦除
  13. 序列化和反序列化
  14. serialVersionUID的作用

下期预告

下一篇将深入讲解《Java集合框架》,包括:

  • HashMap底层原理(数组+链表+红黑树)
  • ConcurrentHashMap的实现机制
  • ArrayList扩容机制
  • LinkedList的实现细节
  • 常见集合类的源码分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值