一、Java 基础 面试题解答(72题)

🔥 高频题(第1~25题)


第1题:Java作为一门编程语言,其核心特点有哪些?

【核心答案】

Java 的核心特点可以概括为以下六点:

特点 说明
跨平台性 通过 JVM + 字节码实现「一次编写,到处运行」
面向对象 封装、继承、多态,一切皆对象(除基本类型)
自动内存管理(GC) 垃圾回收器自动回收不再使用的对象,无需手动释放内存
强类型 + 健壮性 编译时类型检查、异常处理机制、无指针,减少运行时错误
多线程支持 内置 Thread/Runnable,JUC 并发包提供高级并发工具
丰富的生态 Spring、MyBatis、Netty 等成熟框架,适用于企业级开发

【通俗理解】

把 Java 想象成一家国际连锁餐厅:

  • 跨平台 = 同样的菜谱(代码)在不同国家的分店(操作系统)都能做出同样的菜
  • 面向对象 = 厨房分工明确------洗菜(类A)、切菜(类B)、炒菜(类C),各司其职
  • GC = 自动洗碗机,你不用手动洗碗(释放内存)
  • 强类型 = 菜刀就是菜刀,不能当锅铲用,类型严格限制

【常见误区】

误区 :Java 是纯解释型语言,所以很慢

正解:Java 采用「解释 + JIT 编译」混合模式,热点代码被编译为本地机器码后性能接近 C/C++


第2题:Java是如何实现"一次编写,到处运行"的跨平台特性的?

【核心答案】

Java 通过两层抽象实现跨平台:

  1. 编译阶段.java 源文件 → javac 编译 → .class 字节码(与平台无关的中间码)

  2. 运行阶段:不同平台的 JVM 将同样的字节码翻译为对应平台的机器码

    ┌──────────────────────────────────────────────┐
    │ Java 源文件 (.java) │
    └──────────────────┬───────────────────────────┘
    │ javac 编译

    ┌──────────────────────────────────────────────┐
    │ 字节码 (.class) ← 一次编写 │
    └──────┬──────────────┬──────────────┬─────────┘
    │ │ │
    ▼ ▼ ▼
    ┌──────────┐ ┌──────────┐ ┌──────────┐
    │ Linux JVM│ │Windows JVM│ │ macOS JVM│ ← 到处运行
    └──────────┘ └──────────┘ └──────────┘

【通俗理解】

  • 字节码就像「五线谱」------同样的乐谱,无论在中国、美国、法国,钢琴家(JVM)都能演奏
  • 你把字节码想象成一份「国际通用说明书」,各地工厂(JVM)拿到后各自用本地工艺(机器码)生产

【常见误区】

误区 :Java 程序直接编译成机器码运行

正解:先编译成字节码(.class),再由 JVM 的 JIT 编译器或解释器转换为机器码


第3题:请解释JVM、JDK、JRE三者分别是什么,以及它们之间的关系?

【核心答案】

概念 全称 作用 包含内容
JVM Java Virtual Machine 执行字节码,提供运行时环境 类加载器、字节码执行引擎、GC
JRE Java Runtime Environment 运行 Java 程序的最小环境 JVM + 核心类库 (rt.jar)
JDK Java Development Kit 开发 Java 程序的工具包 JRE + 开发工具 (javac, jar, javadoc等)

【关系图解】

复制代码
┌─────────────────────────────────────┐
│                JDK                   │
│  ┌─────────────────────────────┐    │
│  │  javac / javap / jar / ...  │ ← 开发工具    │
│  │  ┌───────────────────────┐  │    │
│  │  │        JRE             │  │    │
│  │  │  ┌──────────────┐     │  │    │
│  │  │  │     JVM       │     │  │    │
│  │  │  │  类库 (rt.jar)│     │  │    │
│  │  │  └──────────────┘     │  │    │
│  │  └───────────────────────┘  │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

包含关系JDK ⊃ JRE ⊃ JVM

【通俗理解】

  • JVM = 汽车的发动机(核心动力)
  • JRE = 发动机 + 轮胎 + 方向盘(能开起来的最小配置)
  • JDK = 整车 + 工具箱(既能开车,也能修车/造车)

第4题:为什么说Java既是编译型语言又是解释型语言?JIT编译器在其中起什么作用?

【核心答案】

Java 的执行分为两个阶段:

复制代码
源代码(.java) ──编译──▶ 字节码(.class) ──解释/JIT编译──▶ 机器码
       ↑                      ↑                    ↑
   编译型语言特征         中间产物          解释+编译混合执行
阶段 做了什么 性质
javac 编译 .java.class 字节码 编译型特征
JVM 解释执行 逐条解释字节码为机器码 解释型特征
JIT 即时编译 热点代码直接编译为机器码并缓存 编译型特征(运行时)

JIT(Just-In-Time)编译器的作用:

  • 监控方法调用频率,将热点代码(频繁执行的代码)直接编译为本地机器码

  • 下次调用时直接执行机器码,无需再次解释,性能大幅提升

  • 典型优化:方法内联、逃逸分析、锁消除等

    解释执行速度: ████████░░ 慢(每次都翻译)
    JIT编译后速度: ████████████████████ 快(只翻译一次,后续直接用)

【通俗理解】

  • Java 像是一个同声传译 + 速记员 的组合:
    • 平常内容逐句翻译(解释执行)
    • 听到重复/重要的话,直接记下来下次复读(JIT 编译)
  • 你第一次唱一首歌需要看歌词(解释),唱多了就直接会了(JIT 缓存)

第5题:值传递和引用传递有什么区别?Java中使用的是哪一种?请举例说明。

【核心答案】

Java 中只有值传递(Pass by Value),没有引用传递!

传递方式 含义 Java 中是否存在
值传递 将实参的值拷贝一份传给形参 ✅ 是
引用传递 将实参的地址直接传给形参(形参和实参指向同一内存地址) ❌ 否

对于基本类型:传递的是值的拷贝

java 复制代码
public static void main(String[] args) {
    int a = 10;
    modify(a);
    System.out.println(a); // 输出 10,没有改变!
}

public static void modify(int x) {
    x = 20;  // 改变的是 x 这个副本
}

对于引用类型 :传递的是引用的拷贝(不是对象本身!)

java 复制代码
public static void main(String[] args) {
    User user = new User("张三");
    modify(user);
    System.out.println(user.name); // 输出 "李四",对象内容变了!
}

public static void modify(User u) {
    u.name = "李四";  // u 是 user 引用的拷贝,但指向同一个对象
    u = new User("王五");  // 改变的是 u 这个副本指向,原 user 不受影响
}

【图解】

复制代码
基本类型(值传递):
  main: a = [10]
              │ 拷贝值
  modify: x = [10] → x = [20]   // 改的是拷贝
  main: a = [10]                // 不受影响

引用类型(传引用拷贝):
  main: user ──→ [User("张三")]   ← 堆中的真实对象
                   ▲
  modify: u ──────┘   // u 是 user 的引用拷贝,指向同一对象
           u.name = "李四"  → 修改了真实对象 ✅
           u = new User("王五") → u 指向新对象,user 不变 ✅

【常见误区】

误区 :Java 基本类型是值传递,引用类型是引用传递

正解:Java 一切都是值传递。引用类型传递的是「引用地址的值」的拷贝,不是对象本身地址的直接传递


第6题:Java的八种基本数据类型分别是什么?每种各占用多少字节?

【核心答案】

数据类型 关键字 占用字节 占用位数 取值范围 默认值
字节型 byte 1 8 -128 ~ 127 0
短整型 short 2 16 -32768 ~ 32767 0
整型 int 4 32 -2³¹ ~ 2³¹-1(约±21亿) 0
长整型 long 8 64 -2⁶³ ~ 2⁶³-1 0L
单精度浮点 float 4 32 ±3.4E-38 ~ ±3.4E+38 0.0f
双精度浮点 double 8 64 ±1.7E-308 ~ ±1.7E+308 0.0d
字符型 char 2 16 0 ~ 65535 (Unicode) '\u0000'
布尔型 boolean 1(虚拟机相关) - true / false false

【记忆口诀】

byte → short → int → long → float → double (从小到大)

1-2-4-8-4-8 字节,char 占 2 字节,boolean 占 1 字节

【自动类型提升顺序】

复制代码
byte → short → int → long → float → double
         ↑
        char

第7题:为什么涉及金额计算时推荐使用BigDecimal而不是double或float?

【核心答案】

因为 floatdouble 采用二进制浮点数 表示,很多十进制小数无法精确表示,会产生精度丢失

java 复制代码
// 经典翻车现场
System.out.println(0.1 + 0.2);      // 输出:0.30000000000000004  ❌
System.out.println(1.0 - 0.9);      // 输出:0.09999999999999998  ❌

// 使用 BigDecimal
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));       // 输出:0.3  ✅

【为什么会精度丢失?】

10 进制的 0.1 转换为 2 进制是无限循环小数:0.0001100110011...,计算机只能用有限的位数近似存储。

【BigDecimal 使用关键点】

java 复制代码
// ❌ 错误:用 double 构造 BigDecimal,精度已经丢失
BigDecimal bad = new BigDecimal(0.1);  // 0.100000000000000005551...

// ✅ 正确:用字符串构造
BigDecimal good = new BigDecimal("0.1");

// ✅ 也可以用 valueOf(内部用了字符串)
BigDecimal alsoGood = BigDecimal.valueOf(0.1);
对比维度 double/float BigDecimal
精度 不精确(二进制近似) 精确(十进制整数存储)
性能 快(硬件支持) 慢(软件计算)
使用场景 科学计算、图形渲染 金融、货币计算
API 复杂度 简单 复杂(加减乘除都用方法)

【常见误区】

误区new BigDecimal(0.1) 就能精确表示 0.1

正解 :double 的 0.1 本身就不精确,构造 BigDecimal 时已经晚了,必须用 new BigDecimal("0.1")


第8题:什么是自动装箱和自动拆箱?使用过程中有哪些常见的坑?

【核心答案】

概念 方向 编译器做的事
自动装箱 (Autoboxing) 基本类型 → 包装类 Integer.valueOf(int)
自动拆箱 (Unboxing) 包装类 → 基本类型 Integer.intValue()
java 复制代码
// 自动装箱:int → Integer
Integer i = 100;           // 编译器生成:Integer.valueOf(100)

// 自动拆箱:Integer → int
int j = i;                 // 编译器生成:i.intValue()

// 运算时自动拆箱
Integer a = 10;
Integer b = 20;
int c = a + b;             // a.intValue() + b.intValue()

【常见坑】

坑1:NullPointerException

java 复制代码
Integer x = null;
int y = x;  // 自动拆箱时调用 x.intValue() → NPE!

坑2:== 比较的是引用,不是值

java 复制代码
Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true(缓存范围内)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false(超出缓存范围,new 了新对象)!

坑3:大量装箱影响性能

java 复制代码
// ❌ 坏习惯:循环中大量装箱
Long sum = 0L;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;  // 每次循环都会装箱/拆箱
}

// ✅ 好习惯:用基本类型
long sum = 0L;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}

第9题:Integer的缓存机制是什么?缓存范围是多少?为什么这样设计?

【核心答案】

Integer 内部维护了一个静态缓存数组 ,默认缓存 [-128, 127] 范围内的 Integer 对象。

java 复制代码
// Integer.valueOf() 源码(简化版)
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) {
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}
java 复制代码
Integer a = 100;  // Integer.valueOf(100) → 从缓存取
Integer b = 100;  // Integer.valueOf(100) → 从缓存取(同一个对象)
Integer c = 200;  // Integer.valueOf(200) → new Integer(200)
Integer d = 200;  // Integer.valueOf(200) → new Integer(200)(新对象)

System.out.println(a == b);  // true  ✅
System.out.println(c == d);  // false ❌ 容易踩坑

【为什么是 -128 ~ 127?】

  • 这个范围覆盖了最常用的小整数(循环计数、数组下标等)
  • 借鉴了「享元模式(Flyweight Pattern)」思想,避免频繁创建销毁小整数对象
  • 这个范围的数据在大多数应用中使用频率最高

【可通过 JVM 参数调整上限】

bash 复制代码
-XX:AutoBoxCacheMax=256   # 将缓存上限调整到 256

【图解】

复制代码
┌─────────────────────────────────────────────┐
│           IntegerCache.cache[]               │
│  ┌────┬────┬────┬────┬────┬────┬────┬────┐  │
│  │-128│-127│ ...│ 0  │ 1  │ ...│ 126│ 127│  │
│  └────┴────┴────┴────┴────┴────┴────┴────┘  │
│         ↑ 缓存命中,返回同一对象               │
│                                 │
│ Integer.valueOf(128) ──────────▶ new Integer(128) │
│   超范围,每次 new 新对象                        │
└─────────────────────────────────────────────┘

【对比其他包装类】

包装类 缓存范围
Byte -128 ~ 127(全部缓存)
Short -128 ~ 127
Integer -128 ~ 127(可调上限)
Long -128 ~ 127
Character 0 ~ 127(ASCII)
Float, Double 无缓存

第10题:请结合代码示例,说明面向对象的三大特性:封装、继承、多态。

【核心答案】

复制代码
面向对象三大特性
├── 封装(Encapsulation)
│       隐藏内部实现,只暴露必要的接口
├── 继承(Inheritance)
│       子类继承父类的属性和方法,实现代码复用
└── 多态(Polymorphism)
        同一个方法调用,不同对象产生不同行为

【1. 封装------安全的数据访问】

java 复制代码
public class BankAccount {
    private double balance;  // 私有字段,外部不能直接访问

    // 只提供受控的访问方式
    public double getBalance() { return balance; }

    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("金额必须大于0");
        }
        balance += amount;
    }

    public void withdraw(double amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("余额不足");
        }
        balance -= amount;
    }
}
// 外部无法 bankAccount.balance = -1000;  ← 被 private 保护

【2. 继承------代码复用】

java 复制代码
// 父类
public class Animal {
    protected String name;

    public void eat() {
        System.out.println(name + " 在吃东西");
    }
}

// 子类继承父类
public class Dog extends Animal {
    public void bark() {
        System.out.println(name + " 汪汪叫");
    }
}

Dog dog = new Dog();
dog.name = "旺财";
dog.eat();   // 继承自 Animal
dog.bark();  // Dog 自己的方法

【3. 多态------同一接口,不同实现】

java 复制代码
// 多态的核心:父类引用指向子类对象
Animal animal1 = new Dog();  // 向上转型
Animal animal2 = new Cat();

animal1.eat();  // 输出:"狗在吃骨头"(调用 Dog 的 eat)
animal2.eat();  // 输出:"猫在吃鱼"(调用 Cat 的 eat)

// 多态的三种形式:
// 1) 继承多态(如上)
// 2) 接口多态
List<String> list = new ArrayList<>(); // 接口引用指向实现类
// 3) 方法重载(编译时多态)
public void print(int x) { ... }
public void print(String s) { ... }

【三者关系图解】

复制代码
        封装
      ┌───────┐
      │ 隐藏实现  │
      │ 保护数据  │
      └───┬───┘
          │ 提供接口
          ▼
        继承 ──────▶ 多态
      ┌───────┐   ┌───────────┐
      │ 复用代码  │──▶│ 灵活替换    │
      │ 建立层级  │   │ 扩展开放    │
      └───────┘   └───────────┘

第11题:方法重载(Overload)和方法重写(Override)有什么区别?各自的规则是什么?

【核心答案】

对比维度 重载 (Overload) 重写 (Override)
定义 同一个类中,方法名相同,参数列表不同 子类重新定义父类的同名方法
发生范围 同一个类(或父子类) 父子类之间
参数列表 必须不同(数量/类型/顺序) 必须相同
返回类型 可以不同 相同或是其子类(协变返回)
访问修饰符 可以任意 不能比父类更严格
异常 可以任意 不能比父类抛出更宽泛的异常
static方法 可以重载 不能重写(可以隐藏)
final方法 可以重载 不能重写
发生时机 编译时确定(编译时多态) 运行时确定(运行时多态)
注解 无需注解 @Override(推荐加上)

【代码示例】

java 复制代码
// ========== 方法重载 ==========
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 Animal makeSound() throws Exception {
        System.out.println("动物叫");
        return this;
    }
}

public class Dog extends Animal {
    @Override  // 编译期检查是否正确重写
    public Dog makeSound() {  // 返回类型可以是子类(协变)
        System.out.println("汪汪汪");
        return this;
    }
}

Animal a = new Dog();
a.makeSound();  // 运行时才确定调 Dog 的方法 → 输出"汪汪汪"

【常见误区】

误区1 :重载可以通过不同的返回类型来区分

正解 :重载只看方法名+参数列表,返回类型不同但参数相同不是重载,会编译报错
误区2 :重写可以改变 static 方法的行为

正解:static 方法属于类,不能被子类重写,只能被「隐藏」(方法隐藏)


第12题:Java中抽象类和接口的区别是什么?在Java 8之后接口有哪些变化?

【核心答案】

对比维度 抽象类 (abstract class) 接口 (interface)
关键字 abstract class interface
继承/实现 单继承 (extends),只能继承一个 多实现 (implements),可实现多个
构造方法 ✅ 有 ❌ 无
成员变量 任意类型(可非 public) 只能 public static final 常量
普通方法 ✅ 可以有 Java 8+ 可以用 default
静态方法 ✅ 可以有 Java 8+ 可以有
私有方法 ✅ 可以有 Java 9+ 可以用 private
设计意图 "is-a" 关系,提取共性 "can-do" 能力,定义规范

【Java 8 之后接口的变化】

复制代码
Java 7 及以前:
  接口中只能有:抽象方法 + 常量

Java 8:
  新增 default 方法(有默认实现)
  新增 static 方法

Java 9:
  新增 private 方法(供 default 方法内部复用)

代码示例:

java 复制代码
// Java 8+ 的接口
public interface PaymentService {

    // 抽象方法(传统接口方法)
    void pay(BigDecimal amount);

    // Java 8: default 方法(有默认实现,子类可重写)
    default void logPayment(BigDecimal amount) {
        validate(amount);     // 调用私有方法
        System.out.println("支付金额:" + amount);
    }

    // Java 8: static 方法
    static PaymentService createAliPay() {
        return new AliPayServiceImpl();
    }

    // Java 9: private 方法
    private void validate(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("金额必须大于0");
        }
    }
}

【选择策略】

复制代码
需要一个模板/骨架? → 抽象类
├── 多个类共享代码 → 抽象类
├── 需要非 public 成员 → 抽象类
├── 需要构造方法初始化 → 抽象类
└── "is-a" 关系 → 抽象类

只需要定义能力/契约? → 接口
├── 不相关的类需要共同行为 → 接口
├── 需要多重继承 → 接口
├── "can-do" 能力 → 接口
└── 解耦(面向接口编程) → 接口

第13题:final关键字在Java中可以修饰类、方法、变量,分别有什么作用?

【核心答案】

修饰对象 作用 效果
final class A {} 不能被继承 (断子绝孙类),如 StringInteger
方法 final void m() {} 方法不能被子类重写
基本类型变量 final int x = 10; 不可修改(常量)
引用类型变量 final User u = new User(); 引用不可变 (不能指向新对象),但对象内部状态可以变
java 复制代码
// ===== 修饰类 =====
public final class StringUtils {
    // 不能被继承,工具类常用
}
// class Sub extends StringUtils {}  ← 编译错误!

// ===== 修饰方法 =====
public class Parent {
    public final void criticalLogic() {
        // 模板方法模式中保护核心算法
    }
}

// ===== 修饰变量 =====
final int MAX_SIZE = 100;        // 基本类型不可变
final List<String> list = new ArrayList<>();
list.add("hello");                // ✅ 对象内容可以变
// list = new ArrayList<>();      // ❌ 引用不可变,编译错误

【图解】

复制代码
final int x = 10;
    x ──→ [10]  ← 10 这个值被锁死

final User u = new User("Tom");
    u ──→ [User(name="Tom")]  ← 这个箭头被锁死
                 │
            name 可以改!

【常见误区】

误区 :final 修饰的引用变量指向的对象也不能修改

正解 :引用不可变 ≠ 对象不可变。final List 不能 = new ArrayList(),但可以 list.add()


第14题:static关键字可以修饰哪些成员?分别产生什么效果?

【核心答案】

static 可以修饰:变量、方法、代码块、内部类 。被 static 修饰的成员属于类级别,而非实例级别。

修饰对象 效果 加载时机 访问方式
静态变量 所有实例共享同一份数据 类加载时 类名.变量
静态方法 只能访问静态成员,不能使用 this 类加载时 类名.方法()
静态代码块 类加载时执行一次,用于初始化静态变量 类加载时 自动执行
静态内部类 不依赖外部类实例,可独立存在 使用时加载 new Outer.Inner()
java 复制代码
public class StaticDemo {
    // 静态变量:所有实例共享
    private static int count = 0;

    // 静态代码块:类加载时执行一次
    static {
        System.out.println("类加载时执行");
        count = 100;
    }

    // 静态方法:只能访问静态成员
    public static int getCount() {
        // instanceMethod();  ← 编译错误,不能调用非静态方法
        return count;
    }

    // 静态内部类
    static class Builder {
        public StaticDemo build() { return new StaticDemo(); }
    }
}

【内存图解】

复制代码
        方法区/元空间
    ┌───────────────────┐
    │  StaticDemo.class │
    │   static count    │ ← 类级别,只有一份
    │   static {}       │
    └───────────────────┘

        堆内存
    ┌───────────────────┐
    │  new StaticDemo() │ ← 实例1 (没有自己的 count)
    │  new StaticDemo() │ ← 实例2 (没有自己的 count)
    └───────────────────┘

第15题:深拷贝和浅拷贝的区别是什么?如何实现一个对象的深拷贝?

【核心答案】

维度 浅拷贝 (Shallow Copy) 深拷贝 (Deep Copy)
基本类型字段 复制值 复制值
引用类型字段 只复制引用地址(指向同一对象) 递归创建新对象(完全独立)
独立性 拷贝对象和原对象共享内部引用对象 拷贝对象和原对象完全独立
实现难度 简单 复杂

【图解】

复制代码
原对象:                          浅拷贝后:
Person {                          Person {
  name: "Tom"        ──复制值──▶    name: "Tom"
  address ──────────┐              address ───────────┐
}                   │             }                    │
                    ▼                                  ▼
              Address {                        同一个 Address!
                city: "北京"                        city: "北京"
              }                                }

深拷贝后:
Person {                          Person {
  name: "Tom"        ──复制值──▶    name: "Tom"
  address ──────────┐              address ──────┐
}                   │             }               │
                    ▼                             ▼
              Address {                    Address {
                city: "北京"                  city: "北京"
              }                            }  ← 独立新对象

【三种实现方式】

java 复制代码
// 方式1:实现 Cloneable + 递归 clone(侵入性强,不推荐)
public class Person implements Cloneable {
    private String name;
    private Address address;

    @Override
    public Person clone() {
        Person p = (Person) super.clone();
        p.address = this.address.clone();  // 递归拷贝引用对象
        return p;
    }
}

// 方式2:序列化/反序列化(推荐,但性能一般)
public static <T> T deepCopy(T obj) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(obj);
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return (T) ois.readObject();
}

// 方式3:第三方库(Jackson / Gson / Fastjson,最常用)
Person copy = objectMapper.readValue(
    objectMapper.writeValueAsString(original), Person.class);
方式 优点 缺点
Cloneable 原生支持 侵入性强,需要递归实现
序列化 通用性强 性能差,所有类需实现 Serializable
JSON 序列化 简单,非侵入 有序列化成本,特殊类型可能丢失

【常见误区】

误区Object.clone() 就是深拷贝

正解Object.clone() 默认是浅拷贝!必须手动递归拷贝内部引用对象才是深拷贝


第16题:== 运算符与 equals() 方法在比较对象时有什么区别?

【核心答案】

对比维度 == equals()
比较内容 比较引用地址(栈中的引用值) 比较对象内容(取决于类的重写实现)
基本类型 比较 不能用(基本类型没有方法)
Object 默认 比较引用地址 比较引用地址(等价于 ==
String 比较引用地址 比较字符串内容(已重写)
自定义类 比较引用地址 取决于是否重写(需手动重写)
java 复制代码
// 基本类型:== 比较值
int a = 10, 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(常量池中同一个对象)

【图解】

复制代码
String s1 = new String("hello");
String s2 = new String("hello");

栈:                          堆:
s1 ──────→ [String "hello"]  ← 地址 0x1000
s2 ──────→ [String "hello"]  ← 地址 0x2000

s1 == s2       → false  (0x1000 != 0x2000)
s1.equals(s2)  → true   (内容都是 "hello")

第17题:hashCode()和equals()方法之间有什么约束关系?为什么重写equals时必须重写hashCode?

【核心答案】

「黄金三定律」------hashCode 和 equals 的约束关系:

规则 说明
规则1 两个对象 equals 为 true → hashCode 必须相等
规则2 两个对象 equals 为 false → hashCode 可以相等也可以不等(等就是哈希冲突)
规则3 同一个对象多次调用 hashCode必须返回相同的 int(前提是 equals 用到的字段没变)

【为什么必须同时重写?------血的教训!】

java 复制代码
public class User {
    private String name;

    // 只重写了 equals,没重写 hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        return Objects.equals(name, ((User) o).name);
    }
    // hashCode 没重写!使用的是 Object 的 native hashCode
}

// 出问题的场景
Map<User, String> map = new HashMap<>();
map.put(new User("张三"), "北京");
System.out.println(map.get(new User("张三")));  // 输出 null !!!

【为什么会返回 null?】

复制代码
HashMap 查找流程:
  get(key) → 计算 key.hashCode() → 定位到桶
          → 在该桶内用 equals() 逐一比较

  put 时: new User("张三").hashCode() = 123456  → 桶#5
  get 时: new User("张三").hashCode() = 789012  → 桶#8
  桶不同!根本找不到!

【正确做法】

java 复制代码
public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);  // 必须使用与 equals 相同的字段
    }
}

第18题:String、StringBuffer、StringBuilder三者的区别是什么?分别在什么场景下使用?

【核心答案】

对比维度 String StringBuffer StringBuilder
可变性 不可变(final char[]/byte[]) 可变 可变
线程安全 ✅ 安全(不可变天然安全) ✅ 安全(synchronized) ❌ 不安全
性能 拼接时创建新对象,最慢 中等(有同步开销) 最快
继承关系 - 继承 AbstractStringBuilder 继承 AbstractStringBuilder
使用场景 字符串常量、少量拼接 多线程环境拼接 单线程环境拼接

【代码示例】

java 复制代码
// String:每次拼接都创建新对象
String s = "Hello";
s += " World";  // 创建了新 String 对象!原 "Hello" 等待 GC

// StringBuffer:线程安全,多线程用
StringBuffer sb1 = new StringBuffer();
sb1.append("Hello").append(" World");  // 同一个对象上操作

// StringBuilder:非线程安全,单线程首选
StringBuilder sb2 = new StringBuilder();
sb2.append("Hello").append(" World");  // 同一个对象上操作,最快

【底层原理】

复制代码
String:
  "Hello"  [H][e][l][l][o]          ← final byte[],不可变
  "Hello World" [H][e][l][l][o][ ][W][o][r][l][d]  ← 全新对象!

StringBuilder:
  [H][e][l][l][o][ ][W][o][r][l][d][ ][ ][ ][ ][ ]  ← 内部可变数组
  直接在原数组上追加,容量不够时扩容(2倍+2)

【选择决策图】

复制代码
需要频繁拼接字符串?
├── 是 → 多线程环境?
│        ├── 是 → StringBuffer
│        └── 否 → StringBuilder
└── 否 → String

第19题:Java 8引入了哪些重要的新特性?请列举并简要说明。

【核心答案】

新特性 说明 解决的问题
Lambda 表达式 (参数) -> { 函数体 } 简化匿名内部类,支持函数式编程
函数式接口 @FunctionalInterface,如 PredicateFunctionConsumerSupplier 配合 Lambda 使用,定义函数签名
Stream API 对集合进行声明式的流式操作 告别 for 循环,链式处理数据
Optional 容器类,优雅处理 null 避免 NullPointerException
方法引用 类名::方法名,如 System.out::println 进一步简化 Lambda
接口默认方法/静态方法 default / static 方法 接口可添加新方法而不破坏实现类
新的日期时间 API LocalDateLocalTimeLocalDateTime(java.time包) 替代难用的 Date/Calendar
Base64 java.util.Base64 官方 Base64 编解码

【Lambda & Stream 代码示例】

java 复制代码
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六");

// 以前:匿名内部类
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// Java 8:Lambda
names.sort((a, b) -> a.compareTo(b));

// Stream:过滤 + 转换 + 收集
List<String> result = names.stream()
    .filter(name -> name.startsWith("张"))   // 中间操作:过滤
    .map(String::toUpperCase)                 // 中间操作:转换
    .collect(Collectors.toList());            // 终端操作:收集
// 结果:["张三"]

【Optional 示例】

java 复制代码
// 以前
User user = getUser();
if (user != null) {
    String name = user.getName();
    if (name != null) {
        return name.toUpperCase();
    }
}
return "Unknown";

// Java 8
return Optional.ofNullable(getUser())
    .map(User::getName)
    .map(String::toUpperCase)
    .orElse("Unknown");

【新日期 API 示例】

java 复制代码
LocalDate today = LocalDate.now();           // 2026-05-17
LocalTime time = LocalTime.of(14, 30);       // 14:30
LocalDateTime dt = LocalDateTime.now();       // 2026-05-17T14:30:00

// 日期计算(不可变,返回新对象)
LocalDate nextWeek = today.plusWeeks(1);
Period period = Period.between(today, nextWeek);  // P7D

第20题:什么是Java反射机制?反射有哪些典型应用场景?它有什么优缺点?

【核心答案】

反射(Reflection) 是 Java 在运行时动态获取类的信息(构造方法、字段、方法、注解等)并操作对象的能力。

复制代码
编译时:不知道具体是什么类
运行时:通过 Class 对象 → 获取构造器 → 创建实例 → 调用方法 → 访问字段

【核心 API 演示】

java 复制代码
// 1. 获取 Class 对象的三种方式
Class<?> clazz1 = User.class;
Class<?> clazz2 = user.getClass();
Class<?> clazz3 = Class.forName("com.example.User");

// 2. 创建实例
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Object obj = constructor.newInstance("张三");

// 3. 调用私有方法
Method method = clazz.getDeclaredMethod("secretMethod");
method.setAccessible(true);  // 突破私有权限
method.invoke(obj);

// 4. 访问私有字段
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
String value = (String) field.get(obj);
field.set(obj, "李四");

【典型应用场景】

场景 说明 例子
框架开发 Spring IoC 通过反射实例化 Bean、注入依赖 @Autowired 注入
动态代理 JDK 动态代理依赖反射调用目标方法 AOP 拦截
注解处理 运行时读取注解并执行对应逻辑 @TableName@RequestMapping
序列化/反序列化 JSON 库通过反射读写对象字段 Jackson、Gson
插件化开发 动态加载类,实现热部署 OSGi、IDEA 插件

【优缺点】

维度 说明
优点 灵活性极高,运行时动态操作;框架的基石
缺点1 性能差(比直接调用慢 10~100 倍),需要安全检查、包装类型
缺点2 破坏封装性,可以绕过访问修饰符
缺点3 编译期无法检查,错误推迟到运行时

第21题:请描述Java的异常体系结构:Error和Exception有什么区别?受检异常与非受检异常分别是什么?

【核心答案】

复制代码
                     Throwable
                    /         \
               Error          Exception
          (严重错误)         /          \
            /    \     RuntimeException   其他 Exception
     OOMError  SOError  (非受检异常)      (受检异常)
                        /        \
                  NullPointer  IllegalArgumentException
                  IndexOutOfBounds
类型 父类 特点 是否需要处理 示例
Error Throwable JVM 级别严重错误,程序无法处理 ❌ 不需要 OutOfMemoryErrorStackOverflowError
受检异常 (Checked) Exception 编译期强制处理 必须 try-catch 或 throws IOExceptionSQLException
非受检异常 (Unchecked) RuntimeException 编译期不检查 ❌ 不需要强制处理 NullPointerExceptionIllegalArgumentException
java 复制代码
// 受检异常:必须处理
public void readFile(String path) throws IOException {
    FileReader fr = new FileReader(path);  // FileReader 构造方法声明了 throws
}

// 非受检异常:可处理可不处理
public int divide(int a, int b) {
    return a / b;  // 可能 ArithmeticException,但编译期不报错
}

【常见误区】

误区 :RuntimeException 不需要捕获,所以可以忽略

正解 :不需要强制捕获 ≠ 不会发生。应该通过代码逻辑预防(如判空),而不是靠 try-catch 掩盖


第22题:双重校验锁(DCL)实现单例模式时,为什么必须使用volatile关键字?

【核心答案】

DCL(Double-Checked Locking)单例中 volatile 防止指令重排导致的「半初始化对象」问题。

java 复制代码
public class Singleton {
    // volatile 是必须的!
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {                    // 第一重检查
            synchronized (Singleton.class) {
                if (instance == null) {            // 第二重检查
                    instance = new Singleton();    // 问题在这里!
                }
            }
        }
        return instance;
    }
}

【为什么 new Singleton() 不是原子操作?】

instance = new Singleton() 在 JVM 层面分为三步:

复制代码
① 分配内存空间
② 调用构造方法,初始化对象
③ instance 引用指向内存地址

正常顺序:① → ② → ③
指令重排:① → ③ → ②   ← 危险!

【指令重排时的灾难场景】

复制代码
线程A:                                             线程B:
① 分配内存
③ instance 指向内存(还没初始化!)
                                                    if (instance == null) → false
                                                    return instance;  ← 拿到半成品!
② 调用构造方法(晚了!)

【volatile 如何解决?】

volatile 提供内存屏障 ,禁止 instance = new Singleton() 的指令重排,保证 ① → ② → ③ 严格有序。

复制代码
有了 volatile:
   ① 分配内存   →   ② 初始化   →   ③ instance 赋值
                     ↑ 内存屏障保证有序 ↑

第23题:BIO(阻塞IO)、NIO(非阻塞IO)、AIO(异步IO)三者的区别和适用场景分别是什么?

【核心答案】

对比维度 BIO NIO AIO
全称 Blocking IO Non-blocking IO Asynchronous IO
IO模型 同步阻塞 同步非阻塞 异步非阻塞
线程模型 一个连接一个线程 一个线程管理多个连接(Selector) 回调/系统通知
API 调用 read() 阻塞等待数据 read() 不阻塞,有数据才读 read() 立即返回,系统完成后回调
并发能力 极高
编程复杂度 简单 复杂 中等
适用场景 连接数少、稳定 高并发、长连接 超高并发、异步处理

【形象比喻】

复制代码
BIO:去餐厅吃饭,你在窗口一直等着,直到菜做好(阻塞)

NIO:你在座位上等,时不时去看看菜好了没(轮询/Selector)

AIO:你坐下刷手机,菜好了服务员会端过来(异步回调)

【图解】

复制代码
BIO: 一个连接一个线程
  Client1 ──▶ Thread1
  Client2 ──▶ Thread2
  Client3 ──▶ Thread3
  (1000个连接需要1000个线程)

NIO: 一个 Selector 管理多个 Channel
  Client1 ──┐
  Client2 ──┼──▶ Selector ──▶ ThreadPool (少量线程)
  Client3 ──┘
  (1000个连接可能只需几个线程)

AIO: 系统完成IO后主动通知
  Client1 ──▶ 发起读请求 → 系统处理 → 回调通知
  (线程完全解放)

第24题:Java如何利用NIO的Channel、Buffer、Selector三大组件实现网络高并发编程?

【核心答案】

NIO 的三大核心组件各司其职:

组件 角色 职责
Channel 通道 数据的双向传输管道(读+写),对应一个连接
Buffer 缓冲区 数据的临时存储区,所有数据都通过 Buffer 读写
Selector 多路复用器 一个线程监控多个 Channel,只处理就绪的 Channel

【工作流程】

复制代码
Selector 线程                         工作线程
     │                                  │
     │ ① 注册 Channel 到 Selector        │
     │    channel.register(selector,     │
     │        SelectionKey.OP_READ)      │
     │                                  │
     │ ② 轮询就绪的 Channel              │
     │    while (true) {                │
     │      selector.select();          │
     │      Set<SelectionKey> keys       │
     │        = selector.selectedKeys();│
     │      for (key : keys) {          │
     │        if (key.isReadable()) {   │
     │          ③ 分发到工作线程 ────────▶ ④ 从 Channel 读数据到 Buffer
     │        }                          │    channel.read(buffer)
     │      }                            │ ⑤ 处理数据
     │    }                              │ ⑥ 写响应数据到 Buffer → Channel
     │                                   │    buffer.flip(); channel.write(buffer)

【代码示例------NIO 服务端】

java 复制代码
// 1. 打开 ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);  // 非阻塞模式

// 2. 创建 Selector
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

// 3. 轮询
while (true) {
    selector.select();  // 阻塞直到有就绪事件
    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
    while (it.hasNext()) {
        SelectionKey key = it.next();
        if (key.isAcceptable()) {
            // 接受新连接
            SocketChannel client = serverChannel.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // 读取数据
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = client.read(buffer);
            if (len > 0) {
                buffer.flip();
                System.out.println(new String(buffer.array(), 0, len));
            }
        }
        it.remove();  // 必须移除,否则下次还会处理
    }
}

【底层原理:epoll 多路复用】

NIO 在 Linux 上底层使用 epoll 系统调用,epoll 通过红黑树 + 就绪链表,将主动轮询变为事件通知,O(1) 获取就绪事件。


第25题:什么是Java泛型?什么是泛型擦除?泛型擦除会带来哪些问题?

【核心答案】

泛型(Generics) 允许类/方法在定义时使用类型参数编译期进行类型检查,确保类型安全。

java 复制代码
// 不用泛型:需要在运行时强制转换,不安全
List list = new ArrayList();
list.add("hello");
list.add(123);         // 不会报错!
String s = (String) list.get(1);  // 运行时 ClassCastException!

// 用泛型:编译期检查
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123);      // 编译错误!类型安全
String s = list.get(0); // 不需要强制转换

【泛型擦除(Type Erasure)】

Java 的泛型是编译期概念。编译后泛型信息会被「擦除」,字节码中不存在泛型,这是为了兼容老版本 JVM。

复制代码
编译前:                                 编译后(擦除后):
List<String> list = new ArrayList<>();   List list = new ArrayList();
list.add("hello");                        list.add("hello");
String s = list.get(0);                  String s = (String) list.get(0);  ← 编译器自动加转型

【泛型擦除带来的问题】

问题1:不能使用基本类型

java 复制代码
// List<int> list = new ArrayList<>();  ← 编译错误!
List<Integer> list = new ArrayList<>(); // 必须用包装类

问题2:无法用 instanceof 检查泛型类型

java 复制代码
List<String> list = new ArrayList<>();
// if (list instanceof List<String>)  ← 编译错误!擦除后只有 List
if (list instanceof List) { }  // 只能这样

问题3:无法创建泛型数组

java 复制代码
// T[] arr = new T[10];  ← 编译错误!编译器不知道 T 是什么
T[] arr = (T[]) new Object[10];  // 只能这样绕过去(有警告)

问题4:方法签名冲突

java 复制代码
// 这两个方法在擦除后签名完全一样,编译错误!
public void print(List<String> list) {}
public void print(List<Integer> list) {}
// 擦除后都变成:public void print(List list) {}

问题5:无法获取泛型的 Class 对象

java 复制代码
// List<String>.class  ← 不存在!
// 运行时只有 List.class

【上下界通配符】

java 复制代码
// ? extends T:上界通配符,只能读不能写
public void readOnly(List<? extends Number> list) {
    Number n = list.get(0);   // ✅ 读取安全
    // list.add(1);            // ❌ 不能写(不知道具体子类型)
}

// ? super T:下界通配符,只能写不能读(精确类型)
public void writeOnly(List<? super Integer> list) {
    list.add(1);              // ✅ 写入安全
    // Integer i = list.get(0); // ❌ 读出来是 Object
}

// PECS 原则:Producer Extends, Consumer Super

⭐ 中频题(第26~72题)


第26题:相比其他编程语言,Java的核心优势和劣势分别是什么?

【核心答案】

维度 优势 ✅ 劣势 ❌
跨平台 JVM 屏蔽操作系统差异 需要安装 JRE,启动慢
内存管理 自动 GC,无需手动释放 GC 暂停影响实时性(ZGC 在改善)
生态 企业级框架极其丰富(Spring、MyBatis) 框架过多,学习成本高
类型安全 强类型 + 编译检查,减少运行时错误 代码冗长,不如动态语言灵活
多线程 JUC 并发包功能强大 相对于 Go 协程较重(虚拟线程在改善)
性能 JIT 编译后接近 C++ 占用内存大,冷启动慢
复制代码
Java 典型优势场景:大型企业后端、分布式系统、大数据(Hadoop/Spark)
Java 不太适合的场景:嵌入式设备、系统编程、前端、AI 模型训练

第27题:JVM(Java虚拟机)和Java语言本身有什么区别?

【核心答案】

对比维度 Java 语言 JVM
是什么 一门编程语言(语法规范) 一个运行时环境(执行字节码)
规范载体 JLS(Java Language Specification) JVMS(JVM Specification)
关注点 语法、语义、类型系统 类加载、字节码执行、内存管理
依赖关系 需要 JVM 来运行 不依赖 Java 语言,可运行 Kotlin/Scala/Groovy 等
复制代码
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  Java 源码   │    │  Kotlin 源码  │    │  Scala 源码  │
└──────┬──────┘    └──────┬──────┘    └──────┬──────┘
       │ 编译              │ 编译              │ 编译
       ▼                   ▼                   ▼
┌─────────────────────────────────────────────────┐
│                字节码 (.class)                    │
│                  ▲        ▲                      │
│                  │        │                      │
│              ┌───┴────────┴───┐                  │
│              │      JVM        │                  │
│              └────────────────┘                  │
└─────────────────────────────────────────────────┘

第28题:JVM是什么?它的核心职责有哪些?

【核心答案】

JVM 是一个虚拟计算机,核心职责有四大块:

职责 说明
类加载 .class 字节码加载到内存(类加载器 + 双亲委派)
字节码执行 解释执行 + JIT 编译,将字节码转为机器码
内存管理 分配内存 + 垃圾回收(GC)
运行时支持 提供 Native 接口、异常处理、线程调度等
复制代码
             ┌─────────────────┐
             │   类加载子系统      │
             └────────┬────────┘
                      ▼
             ┌─────────────────┐
             │   运行时数据区      │
             │  堆 栈 方法区 ...  │
             └────────┬────────┘
                      ▼
             ┌─────────────────┐
             │   字节码执行引擎    │
             │  解释器 + JIT     │
             └─────────────────┘

第29题:编译型语言和解释型语言有什么区别?Java属于哪一种?

类型 工作方式 速度 跨平台 代表
编译型 源码一次编译成机器码,直接运行 C、C++、Go、Rust
解释型 逐行翻译成机器码并执行 Python、JavaScript
Java 源码→字节码(编译)→解释/JIT执行 较快 Java
复制代码
编译型:   源码 ──一次性编译──▶ 机器码 ──直接运行──▶ 输出

解释型:   源码 ──逐行翻译──▶ 机器码 ──逐行运行──▶ 输出

Java:    源码 ──编译──▶ 字节码 ──解释+JIT──▶ 机器码 ──运行──▶ 输出

第30题:Python和Java在语言特性、运行机制、适用场景上有哪些区别?

对比维度 Java Python
类型系统 静态强类型 动态强类型
运行方式 编译 → JVM 执行字节码 解释执行(CPython)
性能 快(JIT 优化后接近 C++) 慢(解释执行,GIL限制多线程)
并发模型 原生多线程 + JUC GIL 限制,多进程/协程
语法 严谨、冗长 简洁、灵活
内存 JVM 自动管理 引用计数 + GC
典型场景 企业级后端、大数据、Android AI/ML、脚本、爬虫、Web快速开发

第31题:int和long类型各占多少位、多少字节?各自的取值范围是多少?

类型 字节 位数 范围
int 4 字节 32 位 -2,147,483,648 ~ 2,147,483,647(约 ±21 亿)
long 8 字节 64 位 -9,223,372,036,854,775,808 ~ ...807(约 ±900 亿亿)
java 复制代码
int max = Integer.MAX_VALUE;   // 2147483647
int min = Integer.MIN_VALUE;   // -2147483648
long maxL = Long.MAX_VALUE;    // 9223372036854775807

第32题:long类型和int类型之间可以互相转换吗?转换时需要注意什么?

可以相互转换,但需要注意数据溢出或精度丢失。

java 复制代码
// int → long:自动转换(安全)
int i = 100;
long l = i;           // 安全,自动提升

// long → int:必须强制转换(可能溢出)
long big = 3000000000L;  // 超出 int 范围
int j = (int) big;       // 溢出!值会变成负数或其他错误值
System.out.println(j);   // 输出一个你意想不到的值

// 安全做法:先检查范围
if (big >= Integer.MIN_VALUE && big <= Integer.MAX_VALUE) {
    int safe = (int) big;
}

第33题:Java中的数据类型转换分为哪几种方式?各自可能引发什么问题?

转换类型 方向 是否自动 风险
自动类型提升 小范围 → 大范围 ✅ 自动 无风险
强制类型转换 大范围 → 小范围 ❌ 需手动 数据溢出、精度丢失
表达式类型提升 不同类型混合运算 ✅ 自动提升为最大类型 浮点数精度问题
java 复制代码
// 自动提升
byte b = 10;
int i = b;            // byte → int 自动

// 强制转换:溢出
int big = 300;
byte small = (byte) big;  // 300 % 256 = 44

// 表达式提升
int a = 5;
double d = 2.0;
double result = a / d;    // int 提升为 double

// 浮点转整数:截断
int x = (int) 3.99;       // x = 3,直接截断,不是四舍五入!

第34题:Java中为什么既要有int基本类型,又要有Integer包装类?二者如何对比?

对比维度 int Integer
类型 基本类型 引用类型(对象)
内存占用 4 字节 16+ 字节(对象头 + 值)
默认值 0 null
性能 慢(有对象开销)
能否用于泛型
能否为 null ✅(表示"无值")
提供方法 ✅(parseInt、valueOf 等)
java 复制代码
// Integer 的必要场景
// 1. 泛型
List<Integer> list = new ArrayList<>();  // 不能 List<int>

// 2. 表示"无值"
Integer score = null;  // 数据库可能为 NULL

// 3. 工具方法
int val = Integer.parseInt("123");
String hex = Integer.toHexString(255);  // "ff"

第35题:既然有了Integer包装类,为什么还要保留int基本类型?

核心原因:性能。

java 复制代码
// 包装类开销对比
int a = 100;           // 4 字节
Integer b = 100;       // 16+ 字节(对象头 8-12 + int值 4 + 对齐填充)

// 性能差异
long start = System.currentTimeMillis();
Long sum = 0L;          // 包装类
for (int i = 0; i < 100_000_000; i++) {
    sum += i;           // 每次循环拆箱→加法→装箱,大量对象创建!
}
// 耗时:几秒(且产生大量 GC)

long sum2 = 0L;         // 基本类型
for (int i = 0; i < 100_000_000; i++) {
    sum2 += i;          // 纯 CPU 运算
}
// 耗时:十几毫秒
保留原因 说明
性能 无对象头开销,直接 CPU 运算
内存 占用极小(4 字节 vs 16+ 字节)
简单 算术运算符直接使用
兼容性 与 C/C++ 底层交互需要

第36题:Java多态体现在哪几个方面?请分别举例说明。

多态体现在三个方面:

多态形式 发生时机 核心
继承多态 运行时 父类引用指向子类对象
接口多态 运行时 接口引用指向实现类对象
方法重载 编译时 同一个方法名,不同参数列表
java 复制代码
// 1. 继承多态
Animal a = new Dog();
a.makeSound();  // 调用 Dog 的 makeSound()
a = new Cat();
a.makeSound();  // 调用 Cat 的 makeSound()

// 2. 接口多态
List<String> list = new ArrayList<>();  // 接口引用 → ArrayList
list = new LinkedList<>();               // 随时切换实现

// 3. 方法重载
public void print(int x) { /* ... */ }
public void print(String x) { /* ... */ }

第37题:多态机制解决了编程中的什么问题?

解决问题 说明 例子
可扩展性 新增子类无需修改调用方代码 新增 Cat 类,Animal 引用代码不变
可替换性 运行时灵活替换实现 List list = new ArrayList()new LinkedList()
解耦 依赖抽象不依赖具体实现 Service 依赖接口而不是具体实现
java 复制代码
// 没有多态:每新增一个动物类型都要改代码
if (type == "dog") dogSound();
else if (type == "cat") catSound();
else if (type == "bird") birdSound();  // 无限膨胀!

// 有多态:新增动物只需新建类,调用代码不用改
Animal animal = zoo.getAnimal();  // 返回什么动物不需要关心
animal.makeSound();               // 自动调用对应的方法

【核心思想------开闭原则】:对扩展开放,对修改关闭。


第38题:面向对象的六大设计原则(SOLID + 迪米特法则)分别是什么?

缩写 原则 核心
S 单一职责 (SRP) 一个类只做一件事
O 开闭原则 (OCP) 对扩展开放,对修改关闭
L 里氏替换 (LSP) 子类可以完全替换父类
I 接口隔离 (ISP) 接口应小而专一
D 依赖倒置 (DIP) 依赖抽象,不依赖具体实现
LoD 迪米特法则 最少知识原则,只和直接朋友通信
java 复制代码
// 单一职责
class OrderService { void createOrder() {} }       // 只管订单
class OrderPrinter  { void printOrder() {} }       // 只管打印

// 开闭原则
interface Shape { double area(); }
class Circle implements Shape { /* ... */ }
// 新增 Rectangle 不需要修改现有 Shape 接口和使用方

// 依赖倒置
class Controller {
    private Service service;  // 依赖接口
    // 不是 private ServiceImpl service; // ❌ 依赖具体实现
}

第39题:抽象类和普通类有什么区别?什么时候应该使用抽象类?

维度 普通类 抽象类
能否实例化 new ❌ 不能
抽象方法 ❌ 不能有 ✅ 可以有
目的 可直接使用 提供模板,让子类补充细节
设计意图 具体实现 抽象模板

使用抽象类的时机

  • 多个类有共同的行为和状态,但某些方法无法给出通用实现
  • 需要模板方法模式
  • 需要构造方法初始化公共状态
java 复制代码
// 抽象类充当模板
public abstract class PaymentProcessor {
    // 公共状态
    protected String merchantId;

    public PaymentProcessor(String merchantId) {
        this.merchantId = merchantId;
    }

    // 模板方法
    public final void process() {
        validate();
        doPay();        // 交给子类实现
        sendNotification();
    }

    private void validate() { /* 公共验证逻辑 */ }
    protected abstract void doPay();  // 子类必须实现
    private void sendNotification() { /* 公共通知逻辑 */ }
}

第40题:抽象类可以用final修饰吗?为什么?

不能。 abstractfinal 是互斥的。

  • abstract 的含义:必须被继承才能使用
  • final 的含义:不能被继承
java 复制代码
// public abstract final class A {}  ← 编译错误!
// abstract 说"我的方法要子类来实现"
// final 说"你不能有子类"
// 矛盾的!

第41题:从Java 8到Java 9,接口中可以定义哪些类型的方法?

方法类型 关键字 引入版本 能否有方法体 能否被重写
抽象方法 (无) Java 1.0 ✅ 必须
default 方法 default Java 8 ✅ 可选
static 方法 static Java 8 ❌(不能被重写)
private 方法 private Java 9 ❌(接口内部用)
java 复制代码
public interface ModernInterface {
    // 抽象方法(所有版本都有)
    void doSomething();

    // Java 8:default 方法
    default void log() {
        privateHelper();  // 调用 Java 9 私有方法
        System.out.println("logging...");
    }

    // Java 8:static 方法
    static ModernInterface create() {
        return new ModernInterface() {
            public void doSomething() {}
        };
    }

    // Java 9:private 方法
    private void privateHelper() {
        System.out.println("private helper");
    }
}

第42题:抽象类可以直接new实例化吗?为什么?

不能。 抽象类中可能包含未实现的抽象方法,直接 new 出来的对象如果调用这些方法,没有代码可执行。

java 复制代码
abstract class Animal {
    abstract void makeSound();  // 没有方法体
}

// Animal a = new Animal();  ← 编译错误!
// 如果允许:a.makeSound() 执行什么??

// 但可以用匿名内部类实例化(本质是创建了子类)
Animal a = new Animal() {  // 创建了 Animal 的匿名子类
    @Override
    void makeSound() {
        System.out.println("汪汪");
    }
};  // 这不是"直接"new 抽象类,而是 new 匿名子类

第43题:接口中能否定义构造函数?为什么?

不能。

  • 构造函数的作用是初始化实例的状态
  • 接口没有实例状态(只有常量),不需要初始化
  • 接口是规范/契约,不是具体实现,不需要被实例化
java 复制代码
public interface MyInterface {
    // public MyInterface() {}  ← 编译错误!
}

// 接口的"初始化"通过实现类构造函数完成
class MyImpl implements MyInterface {
    public MyImpl() {
        // 在这里初始化具体状态
    }
}

第44题:静态变量和静态方法在内存中是如何存储的?它们的加载时机是什么?

项目 存储位置 加载时机 生命周期
静态变量 方法区/元空间(JDK 8+) 类加载的准备阶段 赋默认值,初始化阶段赋真实值 类卸载时释放
静态方法 方法区/元空间 类加载时加载 类卸载时释放
实例变量 堆内存 对象创建时 对象被 GC 回收时
复制代码
内存布局:
┌───────────────┐
│   元空间        │
│  ├─ 类信息      │
│  ├─ 静态变量    │ ← count, MAX_VALUE 等
│  ├─ 静态方法    │ ← staticMethod()
│  └─ 常量池      │
├───────────────┤
│   堆内存        │
│  └─ 实例对象    │ ← new 出来的对象
│      └─ 实例变量 │ ← name, age 等
├───────────────┤
│   栈内存        │
│  └─ 栈帧        │ ← 局部变量
└───────────────┘

第45题:非静态内部类和静态内部类有什么区别?分别如何实例化?

维度 非静态内部类 静态内部类
依赖关系 必须有外部类实例才能存在 不依赖外部类实例
访问外部成员 可以访问外部类的所有成员 只能访问外部类的静态成员
持有外部引用 ✅ 持有 Outer.this ❌ 不持有
内存泄漏风险 ⚠️ 高(持有外部引用) ✅ 低
static 成员 ❌ 不能定义 static 成员 ✅ 可以
java 复制代码
public class Outer {
    private String name = "Outer";
    private static int count = 0;

    // 非静态内部类
    class Inner {
        void print() {
            System.out.println(name);   // ✅ 直接访问外部非静态成员
            System.out.println(count);  // ✅ 访问外部静态成员
        }
    }

    // 静态内部类
    static class StaticInner {
        void print() {
            // System.out.println(name); // ❌ 不能访问外部非静态成员
            System.out.println(count);   // ✅ 只能访问静态成员
        }
    }
}

// 实例化区别
Outer outer = new Outer();
Inner inner = outer.new Inner();              // 先有外部,再有内部
StaticInner staticInner = new Outer.StaticInner();  // 独立实例化

第46题:非静态内部类为什么能直接访问外部类的成员?编译器背后做了什么?

编译器自动为非静态内部类添加了一个指向外部类的引用:Outer this$0

java 复制代码
// 你写的代码:
public class Outer {
    private String name = "hello";

    class Inner {
        void print() {
            System.out.println(name);  // 直接访问
        }
    }
}

// 编译后等效代码:
class Outer {
    private String name;
}

class Outer$Inner {
    final Outer this$0;  // ← 编译器自动加的!

    Outer$Inner(Outer outer) {
        this.this$0 = outer;
    }

    void print() {
        System.out.println(this$0.name);
    }
}

印证方式javap -verbose Outer$Inner.class 可以看到 final Outer this$0 字段


第47题:实现对象深拷贝有哪几种方法?各自优缺点是什么?

(详见第15题------深拷贝与浅拷贝)补充三种方式的对比:

方式 实现 优点 缺点
Cloneable 接口 重写 clone() 递归拷贝 JDK 原生,不需第三方 侵入性强,多层嵌套麻烦
序列化 ObjectOutputStream/ObjectInputStream 通用,自动处理嵌套 所有类需 Serializable,性能差
JSON 序列化 Jackson / Gson / Fastjson 简单,非侵入 性能一般,特殊类型可能丢失
手动拷贝 构造方法 / Builder / 工厂 最可控,最清晰 代码量大
java 复制代码
// 推荐:构造方法方式------最可控
public class User {
    private String name;
    private Address address;

    // 深拷贝构造方法
    public User(User source) {
        this.name = source.name;
        this.address = new Address(source.address);  // 递归拷贝
    }
}

第48题:Java中创建对象有哪几种方式?除了new关键字还有哪些途径?

方式 代码示例 适用场景
new 关键字 new User() 最常用
反射 Class.forName().newInstance()Constructor.newInstance() 框架开发
clone() obj.clone() 按原型复制
反序列化 ObjectInputStream.readObject() 从持久化数据恢复
Unsafe Unsafe.allocateInstance() 底层框架
java 复制代码
// 1. new
User u1 = new User();

// 2. 反射
Class<?> clazz = Class.forName("com.example.User");
User u2 = (User) clazz.getDeclaredConstructor().newInstance();

// 3. clone(需实现 Cloneable)
User u3 = u1.clone();

// 4. 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.obj"));
User u4 = (User) ois.readObject();

// 5. Unsafe(不调用构造方法)
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
User u5 = (User) unsafe.allocateInstance(User.class);  // 不调构造器!

第49题:通过new创建的对象什么时候会被垃圾回收?判断依据是什么?

判断依据:可达性分析算法(GC Roots)。 当一个对象从 GC Roots 出发不可达时,会被标记为垃圾并回收。

复制代码
GC Roots 包括:
├── 虚拟机栈中引用的对象
├── 方法区中静态变量引用的对象
├── 方法区中常量引用的对象
├── 本地方法栈中 JNI 引用的对象
└── 活跃线程对象

可达性分析:
  GC Roots
     │
     ├──▶ objA     ← 可达,不回收
     │     └──▶ objB  ← 可达,不回收
     │
     └──  objC      ← 从 GC Roots 不可达 → 可以回收!
java 复制代码
// 对象不可达的典型场景
public void method() {
    User user = new User();  // user 是 GC Root(栈帧中的引用)
    user = null;              // user 不再指向对象 → 不可达 → 可回收
}  // 方法结束,栈帧销毁,user 引用消失 → 不可达 → 可回收

第50题:如何访问一个类的私有字段或私有方法?有哪些途径?

途径 原理 示例
反射 setAccessible(true) 关闭访问检查 框架开发
getter/setter 私有字段的公开访问方法 日常开发
内部类 编译器桥接方法 语言特性
java 复制代码
public class User {
    private String secret = "密码123";

    private void secretMethod() {
        System.out.println("私有方法被调用");
    }

    // 途径1:getter(推荐)
    public String getSecret() { return secret; }
}

// 途径2:反射(慎用)
User user = new User();
Field field = User.class.getDeclaredField("secret");
field.setAccessible(true);  // 突破私有限制
String value = (String) field.get(user);

Method method = User.class.getDeclaredMethod("secretMethod");
method.setAccessible(true);
method.invoke(user);

⚠️ 注意 :反射破坏封装性,JDK 17+ 默认有模块化限制,需要 --add-opens 参数


第51题:Java注解的底层实现原理是什么?

注解的本质是接口 ,运行时通过动态代理生成代理实例。

复制代码
1. 定义注解
   @Retention(RUNTIME)
   @interface MyAnnotation { String value(); }

2. 编译后生成: interface MyAnnotation extends java.lang.annotation.Annotation { }

3. 运行时获取:动态代理生成 Proxy 类
   调用 getAnnotation(MyAnnotation.class) →
   JDK 动态代理 →
   返回 $Proxy 实例(实现 MyAnnotation)→
   调用代理方法 → AnnotationInvocationHandler.invoke() →
   返回注解属性值
java 复制代码
// 运行时获取注解
MyAnnotation ann = clazz.getAnnotation(MyAnnotation.class);
// ann 的实际类型是:com.sun.proxy.$Proxy1(动态代理对象)
// 调用 ann.value() 时,实际执行的是
// AnnotationInvocationHandler.invoke(proxy, method, args)

第52题:@Retention注解的三种保留策略分别代表什么含义?

策略 含义 保留到何时 典型注解
RetentionPolicy.SOURCE 源码级别,编译时丢弃 .java 文件 @Override, @SuppressWarnings
RetentionPolicy.CLASS 类文件级别,JVM 不加载 .class 文件(默认值) @NonNull(Lombok)
RetentionPolicy.RUNTIME 运行时级别,可通过反射读取 JVM 内存 @Autowired, @RequestMapping
复制代码
生命周期:
  .java 源码 → javac 编译 → .class 字节码 → 类加载 → JVM 运行时
   SOURCE ──✕──                CLASS ──────✕───          RUNTIME ──▶ 可用反射读

第53题:@Target注解可以指定哪些作用域(ElementType)?常用的有哪些?

ElementType 作用域 常用场景
TYPE 类、接口、枚举 @Component, @Entity
FIELD 字段 @Autowired, @Value
METHOD 方法 @GetMapping, @Transactional
PARAMETER 方法参数 @RequestParam, @PathVariable
CONSTRUCTOR 构造方法 @Autowired
LOCAL_VARIABLE 局部变量 较少使用
ANNOTATION_TYPE 注解类型 定义元注解时
PACKAGE 包级别注解
TYPE_PARAMETER 类型参数 <@NonNull T>
TYPE_USE 任何类型使用处 泛型、异常等

第54题:Java中异常处理有哪些方式?try-with-resources语法有什么好处?

方式 语法 说明
try-catch-finally try {} catch {} finally {} 传统方式
try-with-resources try (Resource r = ...) {} 自动关闭资源(Java 7+)
throws 方法签名声明 往上抛给调用者
多层 catch `catch (A B e)`
java 复制代码
// 传统方式:繁琐且容易遗漏关闭
FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // 读文件
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try { fis.close(); } catch (IOException e) { }
    }
}

// try-with-resources:简洁且自动关闭
// 要求资源类实现 AutoCloseable 接口
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}
// fis 和 br 自动关闭,且关闭顺序与打开顺序相反
复制代码
好处:
├── 代码更简洁,减少样板代码
├── 自动关闭资源,不会遗漏
├── 关闭顺序:后打开的先关闭
└── 异常压制机制:try 块异常优先,close 异常作为 suppressed 附加

第55题:为什么有些方法抛出异常时不需要显式使用throws声明?

因为 RuntimeException 及其子类是「非受检异常」,编译期不检查。

java 复制代码
// 不需要 throws(非受检异常)
public int divide(int a, int b) {
    return a / b;  // 可能 ArithmeticException,但不需声明
}

public String getFirst(List<String> list) {
    return list.get(0);  // 可能 IndexOutOfBoundsException,不需声明
}

// 必须 throws(受检异常)
public void readFile(String path) throws IOException {
    new FileReader(path);  // FileReader 声明了 throws IOException
}
异常类型 需要 throws? 原因
RuntimeException / Error 非受检,编译期不强制
其他 Exception 受检异常,编译期强制处理

第56题:如果try块中有return "a",finally块中也有return "b",最终返回什么?为什么?

返回 "b"。finally 中的 return 会覆盖 try 中的 return。

java 复制代码
public static String test() {
    try {
        return "a";
    } finally {
        return "b";
    }
}
// 输出:b
java 复制代码
// 更微妙的例子
public static int test() {
    int x = 1;
    try {
        return x;        // 先把 x 的值(1)缓存在返回地址
    } finally {
        x = 2;           // 改了 x,但返回的是缓存的值
    }
}
// 输出:1  ← 不是 2!

// 但如果返回的是引用类型:
public static User test() {
    User u = new User("Tom");
    try {
        return u;
    } finally {
        u.setName("Jerry");  // 改了对象内容
    }
}
// 返回的 User 的 name 是 "Jerry"!
// 原因是:return 缓存的是引用值,指向对象没变
情况 结果 说明
finally 有 return 返回 finally 的值 覆盖 try 的 return
try return 基本类型 返回 try 瞬时的值 finally 修改无效
try return 引用类型 返回引用(对象可能被 finally 修改) 对象内容可变
finally 抛异常 异常传播,try 的 return 被丢弃 异常覆盖返回

⚠️ 避坑指南:永远不要在 finally 中写 return 或抛异常!


第57题:Object类中有哪些常用方法?各自的作用是什么?

方法 作用
getClass() 获取对象的 Class 对象
hashCode() 返回对象的哈希码(用于 HashMap/HashSet)
equals(Object) 判断两个对象是否「相等」(默认比较地址)
clone() 创建并返回对象的拷贝(需实现 Cloneable,浅拷贝)
toString() 返回对象的字符串表示(默认:类名@十六进制哈希)
notify() 唤醒一个在此对象监视器上等待的线程
notifyAll() 唤醒所有在此对象监视器上等待的线程
wait() / wait(long) / wait(long, int) 使当前线程等待,直到被唤醒
finalize() 对象被 GC 回收前调用(已废弃 JDK 9,JDK 18 彻底移除)
java 复制代码
public class User {
    @Override
    public String toString() {
        return "User{name='" + name + "'}";
    }

    @Override
    public boolean equals(Object o) { /* ... */ }

    @Override
    public int hashCode() { /* ... */ }

    @Override
    protected Object clone() throws CloneNotSupportedException { /* ... */ }
}

第58题:String类有哪些常用的方法?请列举并说明用途。

方法 用途 示例
length() 字符串长度 "abc".length() → 3
charAt(int) 获取指定位置字符 "abc".charAt(0) → 'a'
substring(int, int) 截取子串 "hello".substring(0,2) → "he"
contains(CharSequence) 是否包含 "abc".contains("b") → true
indexOf(String) 第一次出现位置 "abca".indexOf("a") → 0
startsWith/endsWith 前/后缀判断 "abc".startsWith("a") → true
replace/ replaceAll 替换 "a1b2".replaceAll("\\d","") → "ab"
split(String) 分割 "a,b".split(",") → ["a","b"]
trim() 去除首尾空白 " a ".trim() → "a"
toUpperCase/toLowerCase 大小写转换 "Abc".toUpperCase() → "ABC"
equals/equalsIgnoreCase 比较 "a".equals("a") → true
intern() 放入字符串常量池 s.intern()
format(String, Object...) 格式化 String.format("Hi %s", "Tom")
join(CharSeq, CharSeq...) 拼接 String.join("-", "a","b") → "a-b"
isBlank() 是否空白(Java 11+) " ".isBlank() → true
isEmpty() 是否空串 "".isEmpty() → true

第59题:Lambda表达式的基本语法是什么?它解决了什么问题?

Lambda 本质上是「可传递的匿名函数」,用于简化函数式接口的匿名内部类写法。

语法(参数列表) -> { 函数体 }参数 -> 表达式

形式 示例
无参 () -> System.out.println("hello")
单参 x -> x * 2
多参 (a, b) -> a + b
多行 (a, b) -> { int c = a + b; return c; }
java 复制代码
// 以前:匿名内部类
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

// Lambda:简洁明了
Runnable r2 = () -> System.out.println("hello");

// 以前:7 行
list.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

// Lambda:1 行
list.sort((a, b) -> a.length() - b.length());

// 方法引用:更简洁
list.sort(Comparator.comparingInt(String::length));

解决的问题

  • 消灭样板代码(匿名内部类的冗长语法)
  • 支持函数式编程风格
  • 便于并行处理(配合 Stream)

第60题:Stream API有哪些常用操作?中间操作和终端操作有什么区别?

类型 特点 常见操作
中间操作 返回 Stream,惰性执行,可链式调用 filtermapflatMappeekdistinctsortedlimitskip
终端操作 触发计算,消费 Stream,返回结果 collectforEachreducecountanyMatchfindFirstmaxmin
java 复制代码
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六", "张伟");

List<String> result = names.stream()
    .filter(n -> n.startsWith("张"))    // 中间:过滤
    .map(String::toUpperCase)            // 中间:转换
    .sorted()                             // 中间:排序
    .limit(2)                             // 中间:限制数量
    .collect(Collectors.toList());        // 终端:收集 → ["张三", "张伟"]

// 常用终端操作
boolean any = names.stream().anyMatch(n -> n.length() > 2);  // true
long count = names.stream().count();                          // 5
Optional<String> first = names.stream().findFirst();          // 张三

// 常用收集器
Map<Integer, List<String>> byLen = names.stream()
    .collect(Collectors.groupingBy(String::length));
String joined = names.stream()
    .collect(Collectors.joining(", "));  // "张三, 李四, ..."

第61题:ParallelStream是什么?适用于什么场景?使用时需要注意哪些问题?

ParallelStream 是并行流,使用 ForkJoinPool 将任务拆分到多核 CPU 并行执行。

java 复制代码
// 顺序流
list.stream().filter(...).collect(...);   // 单线程

// 并行流
list.parallelStream().filter(...).collect(...);  // ForkJoinPool
维度 说明
适用 大数据量、独立计算、CPU 密集型(无 IO 等待)
不适用 小数据量、有状态操作、IO 密集型、结果依赖顺序

注意问题:

java 复制代码
// ⚠️ 问题1:线程安全问题
List<Integer> list = new ArrayList<>();
IntStream.range(0, 1000)
    .parallel()
    .forEach(list::add);  // ❌ ArrayList 线程不安全!结果不可预测

// ✅ 解决:用线程安全集合或 collect
List<Integer> safe = IntStream.range(0, 1000)
    .parallel()
    .boxed()
    .collect(Collectors.toList());  // ✅ 安全

// ⚠️ 问题2:不要用 parallelStream 做 IO 操作
// parallelStream 使用公共 ForkJoinPool,IO 阻塞会影响其他并行任务

第62题:CompletableFuture是什么?它解决了传统Future的哪些痛点?

CompletableFuture 是 Java 8 引入的异步编程工具,实现了 Future + CompletionStage 接口。

痛点 传统 Future CompletableFuture
结果获取 get() 阻塞等待 thenAccept() 回调,不阻塞
链式处理 ❌ 不支持 thenApply/thenCompose
组合多任务 ❌ 需手动等待 allOf/anyOf
异常处理 仅在 get() 时暴露 exceptionally/handle
手动完成 complete()
java 复制代码
// 传统 Future:阻塞等待
Future<String> future = executorService.submit(() -> {
    Thread.sleep(1000);
    return "结果";
});
String result = future.get();  // 阻塞 1 秒!

// CompletableFuture:异步编排
CompletableFuture.supplyAsync(() -> fetchUser(1L))
    .thenApply(user -> user.getName())        // 转换
    .thenAccept(name -> System.out.println(name))  // 消费
    .exceptionally(e -> {                      // 异常处理
        e.printStackTrace();
        return null;
    });

// 多任务组合
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
all.thenRun(() -> {
    String r1 = f1.join();  // 不抛受检异常
    String r2 = f2.join();
    System.out.println(r1 + " " + r2);
});

第63题:Java 21引入了哪些重要的新特性?虚拟线程(Virtual Thread)的原理是什么?

【主要新特性】

特性 说明
虚拟线程 (Virtual Thread) 轻量级线程,几百万个也不卡
模式匹配 (Record Pattern) 简化 instanceof + 解构
Switch 模式匹配 switch 支持类型+条件匹配
字符串模板 STR."Hello \{name}"
序列化集合 SequencedCollection(可逆序操作)

【虚拟线程核心原理】

复制代码
传统平台线程:              虚拟线程:
  Java Thread                 Virtual Thread
      │                            │
      ▼                            ▼
  OS 线程 (重量级)            Carrier Thread (平台线程)
  创建开销大,切换慢               │
  每个约占用 1MB 栈           ┌────┼────┐
                              ▼    ▼    ▼
                           VT1  VT2  VT3 ... (轻量级)
                           无数虚拟线程共享少量平台线程
对比 平台线程 虚拟线程
成本 高(~1MB 栈 + OS 资源) 极低(按需分配)
数量 几千个 数百万个
阻塞 阻塞 OS 线程 阻塞时自动让出 Carrier
创建 new Thread() Thread.startVirtualThread()
java 复制代码
// 虚拟线程使用
Thread vThread = Thread.startVirtualThread(() -> {
    System.out.println("虚拟线程运行中");
});

// 用 ExecutorService 管理虚拟线程
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 1_000_000; i++) {
        executor.submit(() -> {
            Thread.sleep(1000);  // 阻塞不占 OS 线程!
            return "done";
        });
    }
}

第64题:在进行Java对象序列化时,有哪些推荐的最佳实践?

实践 说明
显式声明 serialVersionUID 避免反序列化时版本不匹配
transient 标记敏感字段 密码等敏感数据不参与序列化
使用 JSON/Protobuf 替代 跨语言、更安全、性能更好
谨慎序列化复杂对象 注意内部对象的序列化
考虑反序列化安全性 防止反序列化漏洞攻击
java 复制代码
public class User implements Serializable {
    // 1. 必须声明 serialVersionUID(用 IDE 生成)
    private static final long serialVersionUID = 1L;

    private String name;

    // 2. 敏感字段用 transient
    private transient String password;

    // 3. 静态变量不会被序列化
    private static int count;

    // 4. 自定义序列化逻辑
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();    // 先写非 transient
        oos.writeObject(encrypt(password));  // 加密后写入
    }

    private void readObject(ObjectInputStream ois)
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        this.password = decrypt((String) ois.readObject());
    }
}

💡 更推荐 :使用 JSON(Jackson/Gson)或 Protobuf 做序列化,不依赖 Java 原生序列化


第65题:Java原生序列化如何实现?serialVersionUID和transient关键字的作用是什么?

java 复制代码
// 实现 Serializable 接口即可
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String password;  // 不参与序列化
}

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User user = (User) ois.readObject();
关键字 作用 典型场景
serialVersionUID 版本控制,反序列化时校验类是否匹配 所有 Serializable 类
transient 标记字段不参与序列化 密码、连接、缓存等
serialVersionUID 场景 结果
不声明 编译器自动生成(类变化后值不同 → 反序列化失败)
显式声明固定值 类有小改动仍可反序列化(需你自己保证兼容)

第66题:代理模式和适配器模式有什么区别?各自的适用场景是什么?

对比维度 代理模式 (Proxy) 适配器模式 (Adapter)
目的 控制对原对象的访问 转换接口以适配客户端
接口 代理与被代理实现相同接口 适配器改变接口
关注点 访问控制、增强功能 兼容性、接口转换
关系 代理持有被代理对象 适配器包装被适配对象
java 复制代码
// ===== 代理模式 =====
// 接口相同,控制访问
interface UserService {
    void save();
}
class UserServiceImpl implements UserService {
    public void save() { /* 实际业务 */ }
}
class UserServiceProxy implements UserService {  // 实现同一接口
    private UserService target;
    public void save() {
        System.out.println("开始事务");  // 增强
        target.save();
        System.out.println("提交事务");  // 增强
    }
}

// ===== 适配器模式 =====
// 接口不同,转换适配
interface TypeC { void chargeTypeC(); }            // 新标准
class LightningPhone { void chargeLightning() {} }  // 旧接口
class LightningAdapter implements TypeC {           // 适配器
    private LightningPhone phone;
    public void chargeTypeC() {
        phone.chargeLightning();  // 转换为旧接口调用
    }
}
复制代码
选择策略:
├── 需要新增功能/控制访问,接口不变 → 代理
└── 需要兼容不同接口 → 适配器

第67题:责任链模式和策略模式有什么区别?分别适用于什么业务场景?

对比维度 责任链模式 (Chain of Responsibility) 策略模式 (Strategy)
目的 多个处理器依次尝试处理请求 从多个算法中选择一个执行
关系 处理器之间形成链条 策略之间互斥独立
结果 可能被多个处理器处理(或截断) 只有一个策略被执行
java 复制代码
// ===== 责任链:审批流程 =====
abstract class Approver {
    protected Approver next;
    public void setNext(Approver next) { this.next = next; }
    public abstract void approve(int amount);
}

class Manager extends Approver {
    public void approve(int amount) {
        if (amount <= 1000) System.out.println("经理审批");
        else if (next != null) next.approve(amount);
    }
}

// ===== 策略:支付方式 =====
interface PayStrategy {
    void pay(int amount);
}
class AliPay implements PayStrategy {
    public void pay(int amount) { System.out.println("支付宝支付" + amount); }
}
class WechatPay implements PayStrategy {
    public void pay(int amount) { System.out.println("微信支付" + amount); }
}

class PaymentContext {
    private PayStrategy strategy;
    public void setStrategy(PayStrategy s) { strategy = s; }
    public void pay(int amount) { strategy.pay(amount); }
}
场景 推荐模式 例子
审批流、拦截器链、过滤器链 责任链 Spring Interceptor、Servlet Filter
支付方式、排序算法、折扣规则 策略 多种支付、多种排序

第68题:NIO的三大核心组件Channel、Buffer、Selector分别起什么作用?

(详见第24题------NIO 实现高并发),在此做对比总结:

组件 比喻 核心职责
Channel 铁路/水管 数据的双向传输通道,连接文件或网络
Buffer 货车/水桶 数据的临时存储容器,所有 I/O 数据必经 Buffer
Selector 调度中心 单线程监控多个 Channel,仅处理就绪的
复制代码
Channel:
├── FileChannel       文件读写
├── SocketChannel     客户端 TCP
├── ServerSocketChannel  服务端 TCP
└── DatagramChannel   UDP

Buffer:
├── ByteBuffer        最常用,字节
├── CharBuffer、IntBuffer、... 
└── MappedByteBuffer  内存映射

第69题:哪些主流框架底层使用了Java NIO技术?

框架 使用方式 说明
Netty 封装 NIO 最流行的网络框架,Dubbo/RocketMQ/Elasticsearch 底层
Tomcat 8+ NIO Connector(默认) 高并发 HTTP 处理
Jetty NIO 嵌入式容器
Undertow NIO (XNIO) WildFly 默认,Spring Boot 可替代 Tomcat
ZooKeeper NIO 分布式协调服务
Kafka NIO + 零拷贝 高吞吐消息队列

💡 Netty 几乎统治了 Java 网络编程:Dubbo、RocketMQ、Elasticsearch、gRPC 都基于 Netty


第70题:如何用Comparable接口对学生列表按分数降序、学号升序排序?

java 复制代码
public class Student implements Comparable<Student> {
    private int id;      // 学号
    private int score;   // 分数

    @Override
    public int compareTo(Student other) {
        // 分数降序
        if (this.score != other.score) {
            return Integer.compare(other.score, this.score);
            // 注意:other 在前 = 降序
        }
        // 分数相同,学号升序
        return Integer.compare(this.id, other.id);
    }
}

// 使用
List<Student> students = new ArrayList<>();
Collections.sort(students);  // 直接排序

// ===== 或用 Comparator(不侵入类) =====
students.sort(Comparator
    .comparingInt(Student::getScore).reversed()   // 分数降序
    .thenComparingInt(Student::getId));            // 学号升序

第71题:Native方法是什么?Java中为什么要使用Native方法?

native 方法是用其他语言(C/C++/汇编)实现的,通过 JNI(Java Native Interface)调用。

java 复制代码
// native 方法声明
public class Thread {
    private native void start0();  // 用 C/C++ 实现,只有声明没有方法体
}

// Object 中的 native 方法
public class Object {
    public native int hashCode();
    protected native Object clone() throws CloneNotSupportedException;
    public final native void notify();
    public final native void wait(long timeout) throws InterruptedException;
}
使用原因 说明
与 OS 交互 线程创建、文件 IO 等必须调用 OS 系统调用
性能关键代码 数学运算、加密算法等
复用 C/C++ 库 已有成熟库不必用 Java 重写
操作硬件 直接访问内存、寄存器等
复制代码
Java ──JNI──▶ C/C++ .so/.dll ──系统调用──▶ OS Kernel

第72题:Java进程如何与操作系统进行交互?从用户态到内核态的调用链路是怎样的?

复制代码
完整的调用链路:

┌─────────────────────────────────────────────┐
│                 用户态 (User Space)            │
│                                              │
│   Java 代码                                   │
│      │  new Thread().start()                 │
│      ▼                                       │
│   JVM (C++ 实现)                              │
│      │  调用 native start0()                  │
│      ▼                                       │
│   JNI 层 (C 代码)                              │
│      │  jvm.dll / libjvm.so                   │
│      ▼                                       │
│   libc / glibc                               │
│      │  pthread_create() / clone()            │
│      ▼                                       │
│   ═══════════════════════════════════════    │
│              系统调用 (syscall)                │
│   ═══════════════════════════════════════    │
│      ▼                                       │
└─────────────────────────────────────────────┘
                   │ clone 系统调用
                   ▼
┌─────────────────────────────────────────────┐
│               内核态 (Kernel Space)           │
│                                              │
│   中断/陷阱门                                  │
│      ▼                                       │
│   系统调用处理程序                              │
│      ▼                                       │
│   do_fork() → 创建内核线程                     │
│      ▼                                       │
│   调度器 分配 CPU 时间片                       │
│      ▼                                       │
│   返回用户态                                   │
└─────────────────────────────────────────────┘

关键步骤

步骤 发生了什么
① Java 调用 new Thread().start() 调用到 JVM 的 Thread 类
② JVM 调用 native start0() 进入 JNI 层 C/C++ 代码
③ C 代码调用 pthread_create()clone() 准备系统调用
用户态 → 内核态切换 触发软中断,CPU 切换到内核模式
⑤ 内核创建线程、分配资源 进程控制块(PCB)、栈、调度
内核态 → 用户态切换 返回用户态,线程进入就绪队列
java 复制代码
// 你写的代码:
new Thread(() -> System.out.println("Hello")).start();

// 底层发生了什么:
// Java Thread.start()
//   → native start0()          [JVM C++ 代码]
//     → JVM_StartThread()      [HotSpot 源码]
//       → os::create_thread()  [OS 相关实现]
//         → pthread_create()   [Linux glibc]
//           → clone() 系统调用  [内核]

📝 总结

以上是对「一、Java 基础」全部 72 道题的详细解答,涵盖:

段落 题号 核心主题
高频 1-12 第1~12题 语言特性、JVM体系、面向对象、抽象类/接口
高频 13-25 第13~25题 final/static、拷贝、比较、字符串、Java8/反射/异常/DCL、IO/NIO、泛型
中频 26-47 第26~47题 语言对比、类型转换、多态/设计原则、抽象类、内部类、深拷贝
中频 48-72 第48~72题 创建对象/GC、注解、异常、Stream/Optional、Java21、序列化、设计模式、NIO、Native
相关推荐
兰令水1 小时前
topcode【随机算法题】【2026.5.16打卡-java版本】
java·数据结构·算法
摇滚侠1 小时前
SpringBoot 面试题 真正的 offer 偏方 Java 基础 Java 高级
java·spring boot·后端
会开花的二叉树1 小时前
Qt信号槽这套机制
开发语言·qt
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第58题】【JVM篇】第18题:讲一下三色标记
java·开发语言·jvm
99乘法口诀万物皆可变1 小时前
面向电池管理系统(BMS)的 C++ 实时仿真内核
开发语言·c++
huaiixinsi1 小时前
Java 后端面试高频题整理(02)
java·开发语言·spring·面试·职场和发展·架构·maven
SilentSamsara1 小时前
自定义上下文管理器实战:数据库连接池、文件锁与超时控制
开发语言·python·算法·青少年编程
小短腿的代码世界1 小时前
从KB到字节:Qt行情数据压缩与传输优化的全链路透视——LZ4、Snappy与自定义二进制协议的极限压榨
开发语言·qt
我只想困告1 小时前
day02-RabbitMQ 2026-05-14
java·spring·rabbitmq