Skip to main content

Java 基础面试题集

总题数: 66道 | 重点领域: 面向对象、基本语法、JVM | 难度分布: 基础到高级

本文档整理了 Java 基础的完整66道面试题目,涵盖面向对象、基本语法、常用API、设计模式等各个方面。


面试题目列表

1. Java 中的序列化和反序列化是什么?

序列化(Serialization) 是将Java对象转换为字节序列的过程,便于在网络上传输或保存到磁盘。

反序列化(Deserialization) 是从字节序列重构Java对象的过程。

实现方式

  • 实现java.io.Serializable接口(标记接口,无需实现方法)
  • 使用ObjectOutputStream进行序列化
  • 使用ObjectInputStream进行反序列化

关键点

  • 使用transient关键字标记不需要序列化的字段
  • serialVersionUID用于版本控制,确保序列化与反序列化的类版本一致
  • 序列化会存储对象的完整对象图(包括引用的对象)
  • 反序列化不会调用构造器

示例

java
1// 序列化
2ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
3oos.writeObject(userObject);
4
5// 反序列化
6ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
7User user = (User)ois.readObject();

2. Java 中 Exception 和 Error 有什么区别?

Exception(异常):

  • 表示程序可以处理的异常情况
  • 分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)
  • 受检异常必须被try-catch或throws声明,如IOException、SQLException
  • 非受检异常是RuntimeException的子类,如NullPointerException、ArrayIndexOutOfBoundsException

Error(错误):

  • 表示严重问题,通常是不可恢复的
  • 程序通常无法处理
  • 例如OutOfMemoryError、StackOverflowError、VirtualMachineError

主要区别:

  1. 可处理性:Exception通常是可以被程序处理的,Error通常是不可恢复的
  2. 来源:Exception通常是程序问题,Error通常是系统或JVM问题
  3. 处理责任:Exception通常需要开发者处理,Error通常是JVM处理

异常体系:

1Throwable
2├── Error
3│ ├── OutOfMemoryError
4│ ├── StackOverflowError
5│ └── ...
6└── Exception
7 ├── IOException (checked)
8 ├── SQLException (checked)
9 └── RuntimeException (unchecked)
10 ├── NullPointerException
11 ├── ArrayIndexOutOfBoundsException
12 └── ...

3. 你认为 Java 的优势是什么?

Java的主要优势包括:

  1. 跨平台性:遵循"一次编写,到处运行"(WORA)的原则,通过JVM实现
  2. 面向对象:完全支持面向对象编程范式,包括封装、继承、多态
  3. 安全性:自带安全管理机制,包括类加载器、字节码验证器和安全管理器
  4. 自动内存管理:垃圾回收机制自动处理内存分配和回收
  5. 丰富的API和生态系统:标准库全面,开源框架丰富
  6. 并发支持:内置线程支持,并发库(如java.util.concurrent)强大
  7. 可靠性和稳定性:成熟语言,大量生产环境验证
  8. 向后兼容性:新版本通常能兼容旧代码
  9. 企业级支持:适合构建大型企业应用
  10. 社区活跃:庞大的开发者社区和丰富的资源

4. 什么是 Java 的多态特性?

多态(Polymorphism) 是Java面向对象的三大特性之一(封装、继承、多态),允许一个对象在不同情况下表现出不同的行为。

多态的类型

  1. 编译时多态(静态多态)

    • 方法重载(Overloading):同一个类中多个同名方法,参数列表不同
    • 在编译时确定调用哪个方法
  2. 运行时多态(动态多态)

    • 方法重写(Overriding):子类重写父类的方法
    • 在运行时根据对象的实际类型确定调用哪个方法

实现多态的必要条件

  1. 继承或实现(子类继承父类或实现接口)
  2. 方法重写(子类重写父类或接口的方法)
  3. 父类引用指向子类对象

示例

java
1// 父类引用指向子类对象
2Animal animal = new Dog();
3animal.makeSound(); // 调用的是Dog类的makeSound方法
4
5// 方法参数多态
6public void feedAnimal(Animal animal) {
7 animal.eat(); // 根据传入的具体动物调用相应的eat方法
8}

多态的好处

  1. 提高代码的可扩展性和复用性
  2. 降低代码耦合度
  3. 使程序更加灵活和模块化
  4. 支持接口编程而非实现编程

5. Java 中的参数传递是按值还是按引用?

Java中的参数传递只有按值传递(pass by value),没有按引用传递。

对于基本类型

  • 传递的是实际值的副本
  • 方法内对参数的修改不会影响原始变量

对于引用类型

  • 传递的是引用的副本(对象引用的值)
  • 方法内对引用本身的修改不会影响原始引用
  • 但可以通过引用副本修改引用对象的内容,这会影响到原对象

示例

java
1public void testPassByValue() {
2 // 基本类型
3 int x = 10;
4 changeValue(x); // x仍然是10
5
6 // 引用类型
7 StringBuilder sb = new StringBuilder("Hello");
8 changeReference(sb); // sb仍然指向原对象
9 modifyReference(sb); // sb的内容变为"Hello World"
10}
11
12void changeValue(int num) {
13 num = 20; // 只改变副本,不影响原值
14}
15
16void changeReference(StringBuilder s) {
17 s = new StringBuilder("Hi"); // 只改变引用副本,不影响原引用
18}
19
20void modifyReference(StringBuilder s) {
21 s.append(" World"); // 修改引用指向的对象内容,会影响原对象
22}

常见误解: 很多人认为Java对象是按引用传递的,这是因为可以通过方法修改对象内容,但严格来说这仍是值传递(传递的是引用的值)。

6. 为什么 Java 不支持多重继承?

Java不支持类的多重继承(一个类继承多个父类),但支持实现多个接口。这样设计的主要原因是:

1. 钻石问题(Diamond Problem)

  • 如果A类有一个方法show(),B和C都继承A并重写show(),D同时继承B和C,调用D.show()会有歧义
  • 哪个父类的方法应该被执行?存在方法冲突和二义性

2. 简化设计

  • 单继承使类层次结构更清晰
  • 避免了多父类带来的复杂性和歧义
  • 降低了维护成本

3. 实际需求的解决方案

  • Java通过接口实现多重继承的功能特性
  • 一个类可以实现多个接口
  • 从Java 8开始,接口可以有默认方法和静态方法
  • 可以使用组合代替继承

示例

java
1// 使用接口实现多重继承功能
2interface Flyable {
3 void fly();
4}
5
6interface Swimmable {
7 void swim();
8}
9
10// 一个类实现多个接口
11class Duck implements Flyable, Swimmable {
12 public void fly() {
13 System.out.println("Duck flying");
14 }
15
16 public void swim() {
17 System.out.println("Duck swimming");
18 }
19}

接口的钻石问题解决: Java 8引入默认方法后,也可能遇到钻石问题,此时需要显式指定要使用哪个接口的默认方法,或者重写该方法。

7. Java 面向对象编程与面向过程编程的区别是什么?

面向过程编程(Procedural Programming)

  • 以过程(算法)为中心,强调步骤和顺序
  • 通过一系列的指令和函数调用完成任务
  • 数据和操作数据的函数是分离的
  • 程序被组织为一系列函数

面向对象编程(Object-Oriented Programming, OOP)

  • 以对象为中心,强调数据和行为的封装
  • 通过对象之间的交互完成任务
  • 数据和操作数据的方法被封装在对象中
  • 程序被组织为一组相互协作的对象

主要区别

  1. 基本单元

    • 面向过程:函数是基本单元
    • 面向对象:类和对象是基本单元
  2. 数据组织

    • 面向过程:数据和函数分离
    • 面向对象:数据和方法封装在对象中
  3. 重用性

    • 面向过程:通过函数复用
    • 面向对象:通过继承和多态实现更灵活的复用
  4. 维护性

    • 面向过程:修改函数可能影响多处
    • 面向对象:封装使修改更加局部化
  5. 抽象级别

    • 面向过程:较低抽象级别,关注如何实现
    • 面向对象:较高抽象级别,关注做什么

Java作为OOP语言的特点

  • 所有代码都在类中
  • 支持封装、继承、多态三大核心特性
  • 提供接口机制实现多重继承功能
  • 强调对象之间的交互而非过程控制

8. Java 方法重载和方法重写之间的区别是什么?

方法重载(Overloading)

  • 在同一个类中定义多个同名但参数不同的方法
  • 编译时多态,编译时根据参数确定调用哪个方法
  • 参数必须不同(类型、数量或顺序)
  • 返回类型可以相同也可以不同
  • 访问修饰符可以相同也可以不同
  • 异常声明可以相同也可以不同

方法重写(Overriding)

  • 子类重新实现父类中已有的方法
  • 运行时多态,运行时根据对象类型确定调用哪个方法
  • 方法签名必须相同(名称和参数列表)
  • 返回类型必须相同或是父类方法返回类型的子类型
  • 访问修饰符不能比父类方法更严格
  • 不能抛出比父类方法更宽泛的检查异常

对比表格

特性方法重载(Overloading)方法重写(Overriding)
发生位置同一个类子类
参数必须不同必须相同
返回类型可以不同必须相同或子类型
访问修饰符可以不同不能更严格
异常可以不同不能更宽泛
多态类型编译时多态运行时多态
绑定静态绑定动态绑定

示例

java
1class Parent {
2 void display(int a) {
3 System.out.println("Parent: " + a);
4 }
5
6 Object getObject() {
7 return new Object();
8 }
9}
10
11class Child extends Parent {
12 // 方法重载
13 void display(int a, int b) {
14 System.out.println("Child: " + a + ", " + b);
15 }
16
17 // 方法重写
18 @Override
19 void display(int a) {
20 System.out.println("Child overriding: " + a);
21 }
22
23 // 合法的返回类型协变
24 @Override
25 String getObject() {
26 return "Child";
27 }
28}

9. 什么是 Java 内部类?它有什么作用?

**内部类(Inner Class)**是定义在另一个类内部的类。Java支持四种内部类:

1. 成员内部类(Member Inner Class)

  • 定义在类的成员位置
  • 可以访问外部类的所有成员(包括私有)
  • 可以被访问修饰符修饰
  • 可以使用外部类.this引用外部类实例
  • 必须通过外部类实例创建
java
1public class Outer {
2 private int outerField = 1;
3
4 class Inner {
5 void display() {
6 System.out.println("OuterField: " + outerField); // 可访问外部类私有成员
7 System.out.println("OuterThis: " + Outer.this);
8 }
9 }
10
11 void createInner() {
12 Inner inner = new Inner(); // 创建内部类实例
13 }
14}
15
16// 外部创建内部类实例
17Outer outer = new Outer();
18Outer.Inner inner = outer.new Inner();

2. 静态内部类(Static Nested Class)

  • 使用static关键字定义
  • 不能访问外部类的非静态成员
  • 不需要外部类实例即可创建
java
1public class Outer {
2 private static int staticOuterField = 1;
3 private int outerField = 2;
4
5 static class StaticNested {
6 void display() {
7 System.out.println(staticOuterField); // 可以访问外部类的静态成员
8 // System.out.println(outerField); // 编译错误,无法访问非静态成员
9 }
10 }
11}
12
13// 创建静态内部类实例
14Outer.StaticNested nested = new Outer.StaticNested();

3. 局部内部类(Local Inner Class)

  • 定义在方法或代码块内
  • 只能在定义它的方法或代码块中使用
  • 可以访问外部类成员和有效final的局部变量
java
1public class Outer {
2 void method(final int param) {
3 final int localVar = 10;
4
5 class LocalInner {
6 void display() {
7 System.out.println(param); // 访问有效final参数
8 System.out.println(localVar); // 访问有效final局部变量
9 }
10 }
11
12 LocalInner inner = new LocalInner();
13 inner.display();
14 }
15}

4. 匿名内部类(Anonymous Inner Class)

  • 没有名字的局部内部类
  • 同时声明和实例化
  • 通常用于创建接口或抽象类的实现
java
1public class Outer {
2 void createRunnable() {
3 Runnable r = new Runnable() {
4 @Override
5 public void run() {
6 System.out.println("Anonymous Inner Class");
7 }
8 };
9 new Thread(r).start();
10 }
11}

内部类的作用

  1. 封装:可以将类封装在另一个类中
  2. 访问控制:内部类可以访问外部类的私有成员
  3. 逻辑分组:将密切相关的类分组在一起
  4. 回调实现:便于实现回调和事件处理
  5. 提高可读性和维护性:代码组织更清晰

10. JDK8 有哪些新特性?

JDK 8(Java SE 8)在2014年发布,带来了许多重要的新特性,是Java语言的一次重大升级:

1. Lambda表达式和函数式接口

  • 允许将函数作为方法参数传递
  • 简化匿名内部类的使用
java
1// 使用Lambda表达式
2Collections.sort(list, (a, b) -> a.compareTo(b));

2. Stream API

  • 支持对集合进行函数式操作
  • 提供过滤、映射、归约等操作
java
1List<String> filtered = items.stream()
2 .filter(item -> item.startsWith("A"))
3 .collect(Collectors.toList());

3. 默认方法和静态方法

  • 接口可以有方法实现(默认方法和静态方法)
  • 解决了接口演化的问题
java
1interface Vehicle {
2 default void print() {
3 System.out.println("I am a vehicle!");
4 }
5
6 static void blowHorn() {
7 System.out.println("Blowing horn!");
8 }
9}

4. 方法引用

  • 可以引用已有方法作为Lambda表达式
java
1// 方法引用示例
2list.forEach(System.out::println);

5. 新的日期和时间API

  • 在java.time包中引入全新的日期时间API
  • 线程安全、不可变、更好的设计
java
1LocalDate today = LocalDate.now();
2LocalDateTime dateTime = LocalDateTime.of(2023, Month.JANUARY, 1, 10, 30);

6. Optional类

  • 更优雅地处理空值
  • 避免NullPointerException
java
1Optional<String> name = Optional.ofNullable(getUserName());
2String result = name.orElse("Unknown");

7. Nashorn JavaScript引擎

  • 新的JavaScript引擎,替代了旧的Rhino引擎
  • 提供更好的性能和兼容性

8. Base64编码API

  • 在java.util包中内置Base64编码支持
java
1String encoded = Base64.getEncoder().encodeToString("Hello".getBytes());

9. 并行数组操作

  • Arrays类中新增并行操作方法
java
1Arrays.parallelSort(array);

10. 注解增强

  • 支持重复注解
  • 支持类型注解(可以在任何使用类型的地方使用注解)

11. JVM改进

  • 移除永久代(PermGen),引入元空间(Metaspace)
  • G1垃圾收集器改进

12. 其他新特性

  • CompletableFuture提供强大的异步编程支持
  • StampedLock提供乐观读锁支持
  • String.join()方法简化字符串连接
  • Files类增强,支持更多文件操作

11. Java 中 String、StringBuffer 和 StringBuilder 的区别是什么?

可变性

  • String:不可变,一旦创建内容不能修改
  • StringBuffer:可变,线程安全
  • StringBuilder:可变,非线程安全,但性能更好

线程安全性

  • String:不可变,因此线程安全
  • StringBuffer:线程安全,方法使用synchronized修饰
  • StringBuilder:非线程安全,没有同步措施

性能比较

  • String:每次操作都会创建新对象,性能最差
  • StringBuffer:同步操作带来额外开销,性能中等
  • StringBuilder:无同步开销,性能最好

适用场景

  • String:适用于少量操作、不经常修改、多线程共享的场景
  • StringBuffer:适用于多线程环境下字符串频繁修改的场景
  • StringBuilder:适用于单线程环境下字符串频繁修改的场景

内部实现

  • 所有实现都是基于字符数组
  • StringBuffer和StringBuilder都继承自AbstractStringBuilder
  • 区别在于StringBuffer的方法使用synchronized关键字修饰

代码示例

java
1// String 每次操作创建新对象
2String str = "Hello";
3str = str + " World"; // 创建了新的String对象
4
5// StringBuffer 同步操作,线程安全
6StringBuffer sbuf = new StringBuffer("Hello");
7sbuf.append(" World"); // 原对象被修改,线程安全
8
9// StringBuilder 非同步操作,性能最佳
10StringBuilder sbld = new StringBuilder("Hello");
11sbld.append(" World"); // 原对象被修改,非线程安全

性能测试(添加10000个字符):

  • StringBuilder: ~10ms
  • StringBuffer: ~100ms
  • String: ~50000ms

12. Java 的 StringBuilder 是怎么实现的?

StringBuilder是Java中用于高效处理可变字符串的类,它的主要实现特点包括:

1. 内部数据结构

  • 内部使用字符数组(char[])存储数据(JDK 9之前)
  • JDK 9及之后使用字节数组(byte[]),采用Latin-1或UTF-16编码
  • 继承自AbstractStringBuilder抽象类

2. 动态扩容机制

  • 初始默认容量为16个字符
  • 当内容超出容量时自动扩容
  • 扩容策略:新容量 = 旧容量 * 2 + 2
  • 容量可以通过构造函数预先指定

3. 非线程安全

  • 没有同步措施保证线程安全
  • 优先考虑性能,没有同步开销
  • 多线程环境需使用StringBuffer

4. 主要操作方法

  • append(): 添加内容到末尾
  • insert(): 在指定位置插入内容
  • delete(): 删除指定范围的内容
  • replace(): 替换指定范围的内容
  • reverse(): 反转字符串内容
  • toString(): 转换为String对象

5. 源码关键片段

java
1// JDK 8中的关键实现(简化版)
2abstract class AbstractStringBuilder {
3 char[] value; // 存储字符
4 int count; // 当前使用的长度
5
6 // 扩容方法
7 void expandCapacity(int minimumCapacity) {
8 int newCapacity = (value.length * 2) + 2;
9 if (newCapacity < minimumCapacity)
10 newCapacity = minimumCapacity;
11 value = Arrays.copyOf(value, newCapacity);
12 }
13}
14
15// StringBuilder继承实现
16public final class StringBuilder extends AbstractStringBuilder {
17 // append方法(无同步)
18 @Override
19 public StringBuilder append(String str) {
20 super.append(str);
21 return this;
22 }
23 // 其他方法...
24}

6. 优化技巧

  • 预估容量以减少扩容次数
  • 链式调用提高可读性和便捷性
  • 单线程环境下优先使用StringBuilder

13. Java 中包装类型和基本类型的区别是什么?

**基本类型(Primitive Types)包装类型(Wrapper Classes)**是Java中两种不同的数据类型系统:

基本类型

  • 直接存储值,非对象
  • 存储在栈内存中
  • 性能更好,内存占用小
  • 不能为null
  • 不具备方法和属性
  • 包括:byte、short、int、long、float、double、char、boolean

包装类型

  • 对基本类型的对象封装
  • 引用存储在栈中,对象存储在堆中
  • 性能较差,内存占用大
  • 可以为null
  • 具有类的方法和属性
  • 包括:Byte、Short、Integer、Long、Float、Double、Character、Boolean

主要区别

特性基本类型包装类型
默认值0/falsenull
可为null
存储位置栈内存堆内存
性能较低
方法调用不支持支持
泛型支持不支持支持

自动装箱与拆箱

  • 自动装箱(Autoboxing):基本类型自动转换为包装类型
  • 自动拆箱(Unboxing):包装类型自动转换为基本类型
java
1// 自动装箱
2Integer num = 10; // int 自动转为 Integer
3
4// 自动拆箱
5int value = num; // Integer 自动转为 int

使用场景

  • 基本类型:对性能要求高、内存敏感、值不为null的场景
  • 包装类型:泛型集合、可能为null的情况、需要使用对象方法的场景

注意事项

  • 包装类型的"=="比较可能出现意外结果(比较对象引用)
  • Integer缓存池(-128到127的值被缓存)会影响"=="比较结果
  • 包装类型涉及装箱/拆箱操作,频繁操作会影响性能

14. 接口和抽象类有什么区别?

接口(Interface)和抽象类(Abstract Class)都是Java中支持抽象的机制,但它们有以下重要区别:

1. 实现与继承

  • 抽象类:类通过extends关键字继承,只能单继承
  • 接口:类通过implements关键字实现,可以实现多个接口

2. 成员特性

  • 抽象类:可以有构造方法、实例变量、普通方法、抽象方法
  • 接口:只能有常量(public static final)、抽象方法、默认方法(Java 8+)、静态方法(Java 8+)、私有方法(Java 9+)

3. 修饰符

  • 抽象类:类和方法可以有各种访问修饰符
  • 接口:方法默认是public abstract,变量默认是public static final

4. 设计目的

  • 抽象类:表示"是什么"(is-a)关系,强调类的继承
  • 接口:表示"能做什么"(can-do)关系,强调行为的契约

5. 使用场景

  • 抽象类:当需要共享代码和状态时
  • 接口:当需要定义契约而不关心具体实现时

6. 默认方法(Java 8+)

  • 抽象类:可以有默认实现的方法
  • 接口:从Java 8开始可以有默认方法(default)和静态方法(static)

代码对比

java
1// 抽象类示例
2abstract class Animal {
3 // 构造方法
4 public Animal() { }
5
6 // 实例变量
7 protected String name;
8
9 // 普通方法
10 public void setName(String name) {
11 this.name = name;
12 }
13
14 // 抽象方法
15 public abstract void makeSound();
16}
17
18// 接口示例
19interface Flyable {
20 // 常量(隐式public static final)
21 int MAX_HEIGHT = 10000;
22
23 // 抽象方法(隐式public abstract)
24 void fly();
25
26 // 默认方法(Java 8+)
27 default void glide() {
28 System.out.println("Gliding...");
29 }
30
31 // 静态方法(Java 8+)
32 static boolean canFly(Object obj) {
33 return obj instanceof Flyable;
34 }
35
36 // 私有方法(Java 9+)
37 private void helper() {
38 System.out.println("Helper method");
39 }
40}

最佳实践

  • 优先使用接口,更灵活
  • 当需要共享代码实现时使用抽象类
  • 使用接口定义类型,使用抽象类提供基础实现
  • 有时可以组合使用:抽象类实现接口,提供部分默认实现

15. JDK 和 JRE 有什么区别?

**JDK (Java Development Kit)JRE (Java Runtime Environment)**是Java平台的两个重要组件:

JDK (Java开发工具包)

  • 包含了开发Java应用程序所需的所有工具
  • 包含JRE,提供运行环境
  • 包含编译器(javac),用于将Java源代码转换为字节码
  • 包含调试器(jdb),用于调试Java程序
  • 包含文档生成器(javadoc),用于生成API文档
  • 包含其他开发工具:jar、javap、jlink等

JRE (Java运行时环境)

  • 提供运行Java应用程序的环境
  • 包含JVM (Java虚拟机),执行Java字节码
  • 包含核心类库,提供标准API
  • 包含运行时库,支持JVM运行
  • 不包含开发工具(如编译器)

组件关系图

1┌────────────────────────────────────┐
2│ JDK (Java Development Kit) │
3│ ┌──────────────────────────────┐ │
4│ │ 开发工具: │ │
5│ │ - javac (编译器) │ │
6│ │ - jdb (调试器) │ │
7│ │ - javadoc (文档生成器) │ │
8│ │ - jar, jlink, jps等 │ │
9│ │ │ │
10│ │ ┌────────────────────────┐ │ │
11│ │ │ JRE │ │ │
12│ │ │ ┌─────────────────┐ │ │ │
13│ │ │ │ Java虚拟机 │ │ │ │
14│ │ │ │ (JVM) │ │ │ │
15│ │ │ └─────────────────┘ │ │ │
16│ │ │ │ │ │
17│ │ │ ┌─────────────────┐ │ │ │
18│ │ │ │ 类库和API │ │ │ │
19│ │ │ └─────────────────┘ │ │ │
20│ │ └────────────────────────┘ │ │
21│ └──────────────────────────────┘ │
22└────────────────────────────────────┘

主要区别

特性JDKJRE
主要用途开发Java程序运行Java程序
包含组件开发工具 + JREJVM + 类库
目标用户开发人员终端用户
安装大小较大较小

实际应用

  • 如果只需要运行Java应用程序,安装JRE即可
  • 如果需要开发Java应用程序,必须安装JDK
  • 大多数开发人员直接安装JDK,因为它包含JRE

版本发展

  • 从JDK 9开始,Oracle改变了版本命名和发布策略
  • JDK现在包含JRE,但不再单独提供JRE下载
  • 可以使用jlink工具创建自定义运行时镜像

16. 你使用过哪些 JDK 提供的工具?

JDK提供了丰富的开发和诊断工具,以下是一些常用工具及其用途:

1. 编译和构建工具

  • javac:Java编译器,将.java源文件编译为.class字节码文件
  • jar:创建和管理JAR(Java Archive)文件,打包Java应用
  • javadoc:从源代码中生成API文档
  • jlink:(Java 9+)创建自定义运行时镜像,生成包含特定模块的精简JRE

2. 运行工具

  • java:运行Java应用程序的启动器
  • javaw:Windows平台上不带控制台的Java启动器
  • jdb:Java调试器,用于调试Java程序
  • jshell:(Java 9+)Java交互式REPL环境

3. 诊断工具

  • jstat:监视JVM统计信息(GC、类加载等)
  • jmap:生成堆转储(heap dump),分析内存使用
  • jstack:生成线程堆栈转储,分析线程状态和死锁
  • jcmd:向运行中的JVM发送诊断命令
  • jconsole:图形化JVM监控工具
  • jvisualvm:更强大的可视化监控、故障排除和分析工具
  • jmc:Java Mission Control,高级监控和管理工具

4. 安全工具

  • keytool:管理密钥和证书,用于安全通信
  • jarsigner:JAR文件签名和验证工具

5. 国际化工具

  • native2ascii:转换包含非ASCII字符的文件

6. 远程方法调用工具

  • rmic:生成远程对象存根和骨架类
  • rmid:启动激活系统守护进程

7. 脚本工具

  • jrunscript:Java命令行脚本外壳

工具使用场景

bash
1# 编译Java源文件
2javac MyClass.java
3
4# 运行Java应用
5java -cp . com.example.MainClass
6
7# 创建JAR包
8jar cf myapp.jar *.class
9
10# 查看运行中的Java进程
11jps -l
12
13# 分析堆内存使用情况
14jmap -heap <pid>
15
16# 检查线程状态和死锁
17jstack <pid>
18
19# 监控GC活动
20jstat -gc <pid> 1000

最常用工具组合

  • 日常开发:javac、java、jar
  • 性能分析:jstat、jmap、jstack、jconsole
  • 问题诊断:jvisualvm、jmc

17. Java 中 hashCode 和 equals 方法是什么?它们与 == 操作符有什么区别?

equals方法

  • 定义在Object类中,用于比较两个对象的内容是否相等
  • 默认实现比较对象的引用(即内存地址)
  • 可被子类重写以实现自定义的相等性比较逻辑
  • 应该满足自反性、对称性、传递性、一致性和非空性

hashCode方法

  • 定义在Object类中,返回对象的哈希码值(一个整数)
  • 默认实现返回对象的内存地址的某种转换(依赖于JVM实现)
  • 用于在哈希表数据结构(如HashMap、HashSet)中高效存储和检索对象
  • 相等的对象必须有相等的哈希码

== 操作符

  • 用于比较基本类型的值或引用类型的引用(内存地址)
  • 对于基本类型:比较实际值是否相等
  • 对于引用类型:比较引用是否指向同一对象

三者的关键区别

特性hashCodeequals==
返回类型intbooleanboolean
比较内容生成对象的哈希值对象的逻辑相等性值相等或引用相等
使用场景哈希数据结构对象内容比较基本类型比较或引用比较

hashCode与equals的关系

  1. 如果两个对象equals比较相等,它们的hashCode值必须相同
  2. 如果两个对象hashCode值相同,它们不一定equals相等
  3. 重写equals方法时必须同时重写hashCode方法

示例代码

java
1public class Person {
2 private String name;
3 private int age;
4
5 // 构造器等代码省略
6
7 @Override
8 public boolean equals(Object obj) {
9 if (this == obj) return true; // 引用相同直接返回true
10 if (obj == null || getClass() != obj.getClass()) return false;
11
12 Person person = (Person) obj;
13 return age == person.age &&
14 Objects.equals(name, person.name);
15 }
16
17 @Override
18 public int hashCode() {
19 return Objects.hash(name, age);
20 }
21}
22
23// 使用示例
24Person p1 = new Person("John", 30);
25Person p2 = new Person("John", 30);
26Person p3 = p1;
27
28System.out.println(p1.equals(p2)); // true (内容相等)
29System.out.println(p1 == p2); // false (不同对象)
30System.out.println(p1 == p3); // true (相同引用)
31System.out.println(p1.hashCode() == p2.hashCode()); // true (内容相等,哈希码相等)

常见错误

  • 只重写equals不重写hashCode
  • 在equals中使用==比较String或其他对象
  • 忽略equals的对称性或传递性

18. Java 中的 hashCode 和 equals 方法之间有什么关系?

hashCode和equals方法之间存在密切的关系,这种关系由Object类的通用约定(general contract)定义:

1. 一致性原则

  • 如果两个对象通过equals方法比较相等,则它们的hashCode方法必须返回相同的值
  • 反之不成立:hashCode相同的两个对象不一定equals相等(哈希冲突)

2. 稳定性原则

  • 在对象的equals比较上使用的属性没有修改的情况下,多次调用hashCode应返回相同的值
  • 不同的程序执行可以产生不同的哈希码(不需要在程序执行之间保持一致)

3. 性能考虑

  • hashCode应该分散良好,尽量减少碰撞
  • hashCode计算应该高效,因为在哈希表中频繁调用

实际应用场景

  • 在HashMap、HashSet等基于哈希的集合类中广泛使用
  • 这些集合先使用hashCode快速确定可能的存储桶
  • 然后使用equals进行精确匹配

哈希集合的工作原理

1步骤1: 计算key的hashCode
2步骤2: 根据hashCode确定存储桶位置
3步骤3: 在该存储桶中用equals方法查找精确匹配

正确实现示例

java
1public class Student {
2 private String id;
3 private String name;
4
5 // 构造器等代码省略
6
7 @Override
8 public boolean equals(Object o) {
9 if (this == o) return true;
10 if (o == null || getClass() != o.getClass()) return false;
11
12 Student student = (Student) o;
13 return Objects.equals(id, student.id); // 仅基于id比较
14 }
15
16 @Override
17 public int hashCode() {
18 return Objects.hash(id); // 仅基于id计算哈希
19 // 注意:equals和hashCode使用相同的字段
20 }
21}

不正确实现的后果

  1. 只重写equals,不重写hashCode

    • 对象在HashMap中可能无法正常查找
    • 可能导致意外的重复元素出现在HashSet中
  2. 只重写hashCode,不重写equals

    • 不符合equals和hashCode的约定
    • 可能导致不一致的行为
  3. equals和hashCode使用不同的字段

    • 可能导致equals相等的对象在哈希集合中被视为不同对象

最佳实践

  • 同时重写equals和hashCode方法
  • 使用相同的字段来决定对象相等性和计算哈希码
  • 考虑使用IDE生成这些方法或使用Lombok等库
  • Java 7+中使用Objects.equals()和Objects.hash()

19. 什么是 Java 中的动态代理?

动态代理是Java反射机制的一部分,允许在运行时创建接口的实现类,而无需在编译时就确定具体的实现类。

动态代理的主要用途

  1. 面向切面编程(AOP)
  2. 远程方法调用(RMI)
  3. 依赖注入框架
  4. 数据库连接和事务管理
  5. 方法调用的日志记录、性能监控、安全控制等

Java中两种主要的动态代理机制

1. JDK动态代理

  • 基于Java反射API实现
  • 只能代理实现了接口的类
  • 通过java.lang.reflect.Proxy类和InvocationHandler接口实现
  • 生成的代理类是接口的实现

JDK动态代理示例

java
1import java.lang.reflect.InvocationHandler;
2import java.lang.reflect.Method;
3import java.lang.reflect.Proxy;
4
5// 定义接口
6interface UserService {
7 void addUser(String name);
8 String findUser(int id);
9}
10
11// 实现类
12class UserServiceImpl implements UserService {
13 @Override
14 public void addUser(String name) {
15 System.out.println("Adding user: " + name);
16 }
17
18 @Override
19 public String findUser(int id) {
20 return "User " + id;
21 }
22}
23
24// 调用处理器
25class LoggingInvocationHandler implements InvocationHandler {
26 private final Object target;
27
28 public LoggingInvocationHandler(Object target) {
29 this.target = target;
30 }
31
32 @Override
33 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
34 System.out.println("Before method: " + method.getName());
35 Object result = method.invoke(target, args);
36 System.out.println("After method: " + method.getName());
37 return result;
38 }
39}
40
41// 使用动态代理
42public class Main {
43 public static void main(String[] args) {
44 // 创建目标对象
45 UserService userService = new UserServiceImpl();
46
47 // 创建调用处理器
48 InvocationHandler handler = new LoggingInvocationHandler(userService);
49
50 // 创建代理对象
51 UserService proxy = (UserService) Proxy.newProxyInstance(
52 UserService.class.getClassLoader(),
53 new Class<?>[] { UserService.class },
54 handler
55 );
56
57 // 通过代理调用方法
58 proxy.addUser("Alice");
59 String user = proxy.findUser(123);
60 }
61}

2. CGLIB代理

  • 基于ASM字节码操作框架
  • 可以代理没有实现接口的类(通过继承)
  • 性能通常优于JDK动态代理
  • 不能代理final类或final方法
  • Spring框架当中如果bean没有实现接口则默认使用CGLIB代理

CGLIB代理示例

java
1import net.sf.cglib.proxy.Enhancer;
2import net.sf.cglib.proxy.MethodInterceptor;
3import net.sf.cglib.proxy.MethodProxy;
4
5import java.lang.reflect.Method;
6
7// 普通类(不实现接口)
8class UserService {
9 public void addUser(String name) {
10 System.out.println("Adding user: " + name);
11 }
12
13 public String findUser(int id) {
14 return "User " + id;
15 }
16}
17
18// 方法拦截器
19class LoggingMethodInterceptor implements MethodInterceptor {
20 @Override
21 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
22 System.out.println("Before method: " + method.getName());
23 Object result = proxy.invokeSuper(obj, args);
24 System.out.println("After method: " + method.getName());
25 return result;
26 }
27}
28
29// 使用CGLIB代理
30public class Main {
31 public static void main(String[] args) {
32 // 创建增强器
33 Enhancer enhancer = new Enhancer();
34 // 设置父类
35 enhancer.setSuperclass(UserService.class);
36 // 设置回调
37 enhancer.setCallback(new LoggingMethodInterceptor());
38
39 // 创建代理对象
40 UserService proxy = (UserService) enhancer.create();
41
42 // 通过代理调用方法
43 proxy.addUser("Bob");
44 String user = proxy.findUser(456);
45 }
46}

JDK代理与CGLIB代理的对比

特性JDK动态代理CGLIB代理
实现方式基于接口基于继承
代理对象类型接口的实现目标类的子类
限制只能代理接口不能代理final类/方法
性能较低较高(尤其多次调用)
内置于JDK否(需要额外依赖)
用途面向接口编程非接口类的代理

20. JDK 动态代理和 CGLIB 动态代理有什么区别?

JDK动态代理和CGLIB动态代理是Java中两种主要的动态代理实现方式,它们具有不同的工作原理和适用场景:

1. 实现原理

  • JDK动态代理:基于Java标准库的java.lang.reflect.Proxy类实现,通过反射机制在运行时生成接口实现类
  • CGLIB动态代理:基于字节码操作库ASM实现,通过生成目标类的子类来实现代理

2. 代理对象创建

  • JDK动态代理

    java
    1Interface proxy = (Interface) Proxy.newProxyInstance(
    2 classLoader,
    3 new Class[] { Interface.class },
    4 new InvocationHandler() {
    5 @Override
    6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    7 // 前置处理
    8 Object result = method.invoke(target, args);
    9 // 后置处理
    10 return result;
    11 }
    12 }
    13);
  • CGLIB动态代理

    java
    1Enhancer enhancer = new Enhancer();
    2enhancer.setSuperclass(TargetClass.class);
    3enhancer.setCallback(new MethodInterceptor() {
    4 @Override
    5 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    6 // 前置处理
    7 Object result = proxy.invokeSuper(obj, args);
    8 // 后置处理
    9 return result;
    10 }
    11});
    12TargetClass proxy = (TargetClass) enhancer.create();

3. 关键区别

区别点JDK动态代理CGLIB动态代理
代理方式接口代理类继承代理
必要条件目标类必须实现接口目标类不需要实现接口
生成方式生成接口的实现类生成目标类的子类
依赖关系JDK内置(rt.jar)第三方库(cglib.jar)
方法调用通过反射调用通过FastClass机制
使用限制只能代理接口方法不能代理final类或方法
调用速度首次调用慢,后续调用一般首次调用慢,后续调用快
内存占用较低较高(生成更多类)

4. 性能对比

  • 首次调用:两者都需要生成代理类,性能相似
  • 后续调用:CGLIB通常更快,因为使用FastClass机制而非反射
  • JDK 8+:JDK代理性能得到显著改进,差距缩小

5. 适用场景

  • JDK动态代理

    • 基于接口的编程
    • 不需要额外依赖
    • 内存受限的环境
    • 代理接口方法
  • CGLIB动态代理

    • 目标类没有实现接口
    • 需要代理非接口方法
    • 性能要求高的场景
    • 需要代理类而非接口

6. 框架使用

  • Spring AOP

    • 优先使用JDK动态代理
    • 目标类没有实现接口时,自动切换到CGLIB代理
    • 可通过proxy-target-class配置强制使用CGLIB
  • Hibernate

    • 使用CGLIB创建延迟加载代理

7. 代码示例对比

java
1// 两种代理方式对比
2
3// 共同的接口
4interface Service {
5 void doSomething();
6}
7
8// 目标类
9class ServiceImpl implements Service {
10 @Override
11 public void doSomething() {
12 System.out.println("Doing something...");
13 }
14}
15
16// JDK动态代理
17Service jdkProxy = (Service) Proxy.newProxyInstance(
18 Service.class.getClassLoader(),
19 new Class<?>[] { Service.class },
20 (proxy, method, args) -> {
21 System.out.println("JDK proxy before");
22 Object result = method.invoke(new ServiceImpl(), args);
23 System.out.println("JDK proxy after");
24 return result;
25 }
26);
27
28// CGLIB动态代理
29Enhancer enhancer = new Enhancer();
30enhancer.setSuperclass(ServiceImpl.class);
31enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
32 System.out.println("CGLIB proxy before");
33 Object result = proxy.invokeSuper(obj, args);
34 System.out.println("CGLIB proxy after");
35 return result;
36});
37Service cglibProxy = (Service) enhancer.create();

总结:选择JDK代理还是CGLIB代理主要取决于目标类是否实现接口、性能要求以及项目依赖情况。

21. Java 中的注解原理是什么?

**注解(Annotation)**是Java 5引入的一种特殊类型的接口,用于为代码添加元数据。它们不会直接影响代码逻辑,但可以被编译器、开发工具和运行时环境使用,实现额外的功能。

1. 注解的定义

java
1// 定义一个简单的注解
2public @interface MyAnnotation {
3 String value() default "";
4 int count() default 0;
5}

2. 元注解:用于注解其他注解的特殊注解。

  • @Retention:定义注解的保留策略

    • SOURCE:仅在源码中保留,编译时丢弃
    • CLASS:保留到编译后的类文件,但运行时不可用(默认值)
    • RUNTIME:运行时可通过反射访问
  • @Target:定义注解可应用的元素类型

    • TYPE:类、接口、枚举
    • FIELD:字段
    • METHOD:方法
    • PARAMETER:方法参数
    • CONSTRUCTOR:构造器
    • LOCAL_VARIABLE:局部变量
    • ANNOTATION_TYPE:注解
    • PACKAGE:包
    • TYPE_PARAMETER(Java 8):类型参数
    • TYPE_USE(Java 8):类型使用
  • @Documented:指示注解应被JavaDoc工具记录

  • @Inherited:表示注解可被子类继承

  • @Repeatable(Java 8):表示注解可在同一元素上多次使用

3. 注解的工作原理

编译时处理

  • 编译器在编译时识别注解
  • 可通过注解处理器(Annotation Processor)在编译期处理注解
  • 注解处理器可生成新的源文件、修改编译过程等

运行时处理

  • 使用Java反射API获取运行时注解信息
  • 运行时根据注解信息修改程序行为

4. 反射获取注解示例

java
1// 获取类上的注解
2Class<?> clazz = MyClass.class;
3MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
4if (annotation != null) {
5 System.out.println(annotation.value());
6 System.out.println(annotation.count());
7}
8
9// 获取方法上的注解
10Method method = clazz.getMethod("myMethod");
11MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);
12
13// 获取字段上的注解
14Field field = clazz.getDeclaredField("myField");
15MyAnnotation fieldAnnotation = field.getAnnotation(MyAnnotation.class);

5. 注解处理器

  • 实现javax.annotation.processing.Processor接口
  • 在编译时对代码中的注解进行处理
  • 用于生成额外代码、验证或文档生成等
  • 通过SPI机制注册处理器
java
1@SupportedAnnotationTypes("com.example.MyAnnotation")
2@SupportedSourceVersion(SourceVersion.RELEASE_8)
3public class MyProcessor extends AbstractProcessor {
4 @Override
5 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
6 // 处理注解逻辑
7 return true;
8 }
9}

6. 注解的实现机制

  • 注解本质上是特殊的接口,继承自java.lang.annotation.Annotation
  • 编译器会为每个注解生成代理类,实现注解接口
  • 注解信息被存储在类文件的属性表中
  • JVM加载类时,会解析这些属性并构建内存中的注解对象

7. 注解的应用场景

  • 配置信息:如Spring的@Component, @Autowired
  • 编译检查:如@Override, @Deprecated
  • 代码生成:如Lombok的@Data, @Builder
  • 运行时处理:如JUnit的@Test, @Before
  • API文档:如Swagger的@Api, @ApiOperation

8. 常见的内置注解

  • @Override:声明方法覆盖父类方法
  • @Deprecated:标记已过时的元素
  • @SuppressWarnings:抑制编译器警告
  • @SafeVarargs:抑制与可变参数相关的警告
  • @FunctionalInterface:声明函数式接口

9. Java 8后的注解增强

  • 类型注解:可以在任何使用类型的地方使用注解
  • 重复注解:同一元素可以多次应用相同的注解
  • 方法参数反射:可以获取方法参数的名称

22. 你使用过 Java 的反射机制吗?如何应用反射?

**反射(Reflection)**是Java的一个强大特性,允许程序在运行时检查和操作类、接口、字段和方法。反射打破了Java的封装性,使程序能够访问原本无法访问的成员,并在运行时动态地创建对象和调用方法。

1. 反射的核心类

  • Class类:表示类的元信息,是反射的入口点
  • Constructor:表示类的构造方法
  • Method:表示类的方法
  • Field:表示类的字段
  • Parameter(Java 8):表示方法参数
  • Annotation:表示注解

2. 获取Class对象的方式

java
1// 方式1:通过对象的getClass()方法
2String str = "Hello";
3Class<?> clazz1 = str.getClass();
4
5// 方式2:通过类字面常量
6Class<?> clazz2 = String.class;
7
8// 方式3:通过Class.forName()方法
9Class<?> clazz3 = Class.forName("java.lang.String");
10
11// 方式4:通过类加载器
12ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
13Class<?> clazz4 = classLoader.loadClass("java.lang.String");

3. 反射创建对象

java
1// 使用Class.newInstance()方法(已过时)
2Object obj1 = clazz.newInstance();
3
4// 获取Constructor并创建对象
5Constructor<?> constructor1 = clazz.getConstructor(); // 无参构造器
6Object obj2 = constructor1.newInstance();
7
8// 带参数的构造器
9Constructor<?> constructor2 = clazz.getConstructor(String.class);
10Object obj3 = constructor2.newInstance("Hello");

4. 访问和修改字段

java
1// 获取公共字段
2Field publicField = clazz.getField("fieldName");
3
4// 获取任何字段(包括private)
5Field privateField = clazz.getDeclaredField("privatefield");
6privateField.setAccessible(true); // 绕过访问限制
7
8// 获取字段值
9Object value = privateField.get(obj);
10
11// 设置字段值
12privateField.set(obj, newValue);

5. 调用方法

java
1// 获取公共方法
2Method publicMethod = clazz.getMethod("methodName", paramTypes);
3
4// 获取任何方法(包括private)
5Method privateMethod = clazz.getDeclaredMethod("privateMethod", paramTypes);
6privateMethod.setAccessible(true); // 绕过访问限制
7
8// 调用方法
9Object result = privateMethod.invoke(obj, args);
10
11// 调用静态方法
12Object result = staticMethod.invoke(null, args);

6. 获取泛型信息

java
1// 获取字段的泛型类型
2Field field = clazz.getDeclaredField("listField");
3Type genericType = field.getGenericType();
4
5if (genericType instanceof ParameterizedType) {
6 ParameterizedType pType = (ParameterizedType) genericType;
7 Type[] typeArguments = pType.getActualTypeArguments();
8 for (Type typeArgument : typeArguments) {
9 Class<?> typeClass = (Class<?>) typeArgument;
10 System.out.println("泛型类型: " + typeClass);
11 }
12}

7. 反射获取注解

java
1// 获取类上的注解
2Annotation[] annotations = clazz.getAnnotations();
3
4// 获取指定类型的注解
5MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
6
7// 获取方法上的注解
8Method method = clazz.getMethod("myMethod");
9MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);

8. 反射的应用场景

  • 框架开发

    • Spring的依赖注入和AOP
    • Hibernate的ORM映射
    • JUnit的测试运行器
  • 动态代理

    • JDK动态代理使用反射实现接口方法调用
    • 实现AOP切面功能
  • 序列化与反序列化

    • JSON/XML与对象的相互转换
  • 配置文件处理

    • 根据配置动态加载和使用类
  • 注解处理

    • 运行时解析注解并执行相应逻辑

9. 反射的优缺点

优点

  • 提高了程序的灵活性和扩展性
  • 使框架开发成为可能
  • 支持动态加载和使用类

缺点

  • 性能开销较大,反射操作比直接调用慢
  • 破坏了封装性,可能导致安全问题
  • 代码可读性和可维护性降低
  • 编译时类型检查失效,可能导致运行时错误

10. 提高反射性能的技巧

  • 缓存Constructor/Method/Field对象
  • 减少反射调用次数
  • 使用setAccessible(true)提高私有成员访问速度

11. 反射的安全限制

  • 需要适当的权限才能反射访问私有成员
  • SecurityManager可以限制反射操作
  • Java模块系统(Java 9+)可以控制反射访问

23. 什么是 Java 中的不可变类?

**不可变类(Immutable Class)**是指一旦创建,其实例的状态就不能被改变的类。在Java中,String、Integer等包装类以及BigDecimal、BigInteger都是不可变类的典型例子。

1. 不可变类的特点

  • 实例创建后状态不能被修改
  • 所有字段都是final的(最佳实践)
  • 适当地封装字段(通常是private)
  • 不提供修改状态的方法
  • 保护性地管理任何可变组件
  • 必要时创建对象的防御性拷贝

2. 创建不可变类的规则

  1. 将类声明为final,防止子类化
  2. 将所有字段声明为private final
  3. 不提供修改字段的方法
  4. 特别注意包含可变对象引用的字段
    • 构造器中创建可变对象的副本
    • getter方法返回可变对象的副本
  5. 确保正确处理对象的序列化(如果需要可序列化)

3. 不可变类的示例

java
1public final class ImmutablePerson {
2 private final String name;
3 private final int age;
4 private final List<String> hobbies; // 可变对象引用
5
6 public ImmutablePerson(String name, int age, List<String> hobbies) {
7 this.name = name;
8 this.age = age;
9 // 创建防御性副本
10 this.hobbies = new ArrayList<>(hobbies);
11 }
12
13 public String getName() {
14 return name;
15 }
16
17 public int getAge() {
18 return age;
19 }
20
21 public List<String> getHobbies() {
22 // 返回防御性副本
23 return new ArrayList<>(hobbies);
24 }
25
26 // 不提供setter方法
27
28 // 提供修改状态的方法时返回新实例
29 public ImmutablePerson withName(String newName) {
30 return new ImmutablePerson(newName, age, hobbies);
31 }
32
33 public ImmutablePerson withAge(int newAge) {
34 return new ImmutablePerson(name, newAge, hobbies);
35 }
36}

4. String类的不可变性

String是Java中最常用的不可变类:

  • String类被声明为final
  • 内部字符数组是private final的
  • 没有修改内部状态的方法
  • 所有修改操作都返回新的String对象
java
1public final class String implements Serializable, Comparable<String>, CharSequence {
2 private final char[] value; // JDK 9之前
3 // 或
4 private final byte[] value; // JDK 9之后
5
6 // 其他字段和方法
7}

5. 不可变类的优势

  1. 线程安全

    • 不可变对象天生是线程安全的,无需同步
    • 可以在多线程间自由共享
  2. 简化并发编程

    • 不存在竞态条件
    • 可以安全地用作HashMap的键
  3. 防御性拷贝不再需要

    • 状态不会改变,因此不需要防御性拷贝
    • 但仍需注意可变组件
  4. 失败原子性

    • 操作要么成功,要么失败,没有中间状态
  5. 缓存友好

    • 对象不变,哈希码可以缓存

6. 不可变类的局限性

  1. 为每个不同的值创建新对象

    • 可能导致过多对象创建
    • 在某些场景下有性能影响
  2. 构造复杂对象系统可能较为繁琐

    • 每次修改都需要创建新实例

7. 不可变类设计模式

  1. Builder模式
    • 用于创建复杂的不可变对象
    • 分离构建过程和表示
java
1public final class ImmutablePerson {
2 private final String name;
3 private final int age;
4 private final List<String> hobbies;
5
6 private ImmutablePerson(Builder builder) {
7 this.name = builder.name;
8 this.age = builder.age;
9 this.hobbies = new ArrayList<>(builder.hobbies);
10 }
11
12 // Getters...
13
14 public static class Builder {
15 private String name;
16 private int age;
17 private List<String> hobbies = new ArrayList<>();
18
19 public Builder name(String name) {
20 this.name = name;
21 return this;
22 }
23
24 public Builder age(int age) {
25 this.age = age;
26 return this;
27 }
28
29 public Builder addHobby(String hobby) {
30 this.hobbies.add(hobby);
31 return this;
32 }
33
34 public ImmutablePerson build() {
35 return new ImmutablePerson(this);
36 }
37 }
38}
  1. 工厂方法
    • 提供静态工厂方法创建对象
    • 隐藏实现细节,增强API灵活性

8. 在现代Java中的应用

  • Java Records (JDK 16+)提供了创建不可变数据类的简洁方式
  • Collections.unmodifiableXxx()方法创建不可变集合视图
  • Stream API的使用减少了创建中间不可变对象的需求
  • Collectors.toUnmodifiableXxx()收集到不可变集合

24. 什么是 Java 的 SPI(Service Provider Interface)机制?

**SPI(Service Provider Interface)**是Java提供的一种服务发现机制,允许应用程序动态地发现和加载服务提供者的实现。SPI机制通过在classpath中的特定目录下定义接口的实现来工作,使得服务提供者和服务使用者解耦。

1. SPI的基本概念

  • 服务接口:定义服务的行为和能力
  • 服务提供者:实现服务接口的具体类
  • 服务加载器:用于发现和加载服务实现的工具类(java.util.ServiceLoader)

2. SPI的工作流程

  1. 定义服务接口
  2. 创建接口的实现类
  3. 在META-INF/services/目录下创建以接口全限定名命名的文件
  4. 在文件中列出实现类的全限定名
  5. 使用ServiceLoader加载实现类

3. 简单示例

1) 定义服务接口

java
1package com.example.spi;
2
3public interface MessageService {
4 String getMessage();
5}

2) 创建实现类

java
1package com.example.spi.impl;
2
3import com.example.spi.MessageService;
4
5public class EmailMessageService implements MessageService {
6 @Override
7 public String getMessage() {
8 return "Email Message";
9 }
10}
11
12public class SmsMessageService implements MessageService {
13 @Override
14 public String getMessage() {
15 return "SMS Message";
16 }
17}

3) 创建配置文件: 在META-INF/services/com.example.spi.MessageService文件中添加:

1com.example.spi.impl.EmailMessageService
2com.example.spi.impl.SmsMessageService

4) 使用ServiceLoader加载服务

java
1ServiceLoader<MessageService> serviceLoader = ServiceLoader.load(MessageService.class);
2for (MessageService service : serviceLoader) {
3 System.out.println(service.getMessage());
4}

4. SPI在Java核心库中的应用

  1. JDBC驱动加载

    • java.sql.Driver接口
    • 各数据库厂商提供Driver实现
    • DriverManager通过SPI加载驱动
  2. 日志框架集成

    • java.util.logging.spi包
    • 替换默认日志实现
  3. Java扩展机制

    • javax.imageio.spi包的图像I/O
    • javax.sound.sampled.spi的声音系统
  4. Java加密扩展(JCE)

    • java.security.Provider实现
    • 加密算法提供者

5. Java 9 模块系统中的SPI

Java 9引入了模块系统(JPMS),SPI机制有所变化:

  • 使用provides...with...声明服务提供者
  • 使用uses声明服务使用者
java
1// 模块声明文件(module-info.java)
2module com.example.provider {
3 exports com.example.spi;
4 provides com.example.spi.MessageService with
5 com.example.spi.impl.EmailMessageService,
6 com.example.spi.impl.SmsMessageService;
7}
8
9module com.example.consumer {
10 requires com.example.provider;
11 uses com.example.spi.MessageService;
12}

6. SPI的特点和优势

  • 解耦:服务接口与实现分离
  • 可扩展:不修改原有代码的情况下增加新功能
  • 灵活性:运行时动态选择实现
  • 面向接口编程:基于接口而非实现
  • 动态服务发现:无需硬编码依赖关系

7. SPI的局限性

  • 延迟加载问题:ServiceLoader会一次性实例化所有服务
  • 无法按需加载:缺乏参数化的初始化机制
  • 异常处理复杂:创建实例时的异常难以定位
  • 不支持命名服务:无法根据名称获取指定实现
  • 线程安全问题:ServiceLoader非线程安全

8. SPI的实际应用场景

  • 数据库驱动:MySQL、PostgreSQL等JDBC驱动
  • 日志框架:SLF4J、Log4j等
  • 加密服务提供者:不同的安全算法实现
  • 编译器插件:如Java编译器的注解处理器
  • 编解码器:音频、视频编解码器

9. Spring与SPI

Spring框架提供了类似的机制:

  • Spring的@Component扫描机制
  • Spring的FactoryBean接口
  • Spring Boot的自动配置

10. 与其他设计模式的关系

  • 工厂模式:ServiceLoader作为工厂创建服务实例
  • 策略模式:不同实现代表不同策略
  • 适配器模式:可用于适配不同服务实现
  • 依赖注入:实现服务的依赖反转

25. Java 泛型的作用是什么?

**泛型(Generics)**是Java 5引入的一种特性,允许在定义类、接口和方法时使用类型参数,提供编译时类型安全检查,消除强制类型转换,增强代码的可读性和重用性。

1. 泛型的主要作用

  1. 类型安全

    • 编译时类型检查,避免运行时ClassCastException
    • 错误提前暴露到编译阶段
  2. 消除强制类型转换

    • 自动完成类型转换,减少冗余代码
    • 提高代码可读性和可维护性
  3. 实现通用算法

    • 编写适用于多种类型的通用代码
    • 实现"一次编写,多处使用"

2. 泛型的基本用法

泛型类

java
1// 定义泛型类
2public class Box<T> {
3 private T value;
4
5 public void set(T value) {
6 this.value = value;
7 }
8
9 public T get() {
10 return value;
11 }
12}
13
14// 使用泛型类
15Box<Integer> intBox = new Box<>();
16intBox.set(123);
17Integer value = intBox.get(); // 无需类型转换
18
19Box<String> strBox = new Box<>();
20strBox.set("Hello");
21String text = strBox.get(); // 无需类型转换

泛型方法

java
1// 定义泛型方法
2public <E> void printArray(E[] array) {
3 for (E element : array) {
4 System.out.println(element);
5 }
6}
7
8// 使用泛型方法
9Integer[] intArray = {1, 2, 3};
10printArray(intArray);
11
12String[] strArray = {"Hello", "World"};
13printArray(strArray);

泛型接口

java
1// 定义泛型接口
2public interface Comparable<T> {
3 int compareTo(T o);
4}
5
6// 实现泛型接口
7public class Person implements Comparable<Person> {
8 private String name;
9 private int age;
10
11 @Override
12 public int compareTo(Person other) {
13 return Integer.compare(this.age, other.age);
14 }
15}

3. 泛型通配符

  • 无界通配符 <?>
    • 表示任何类型
    • 主要用于不需要写入的场景
java
1public void printList(List<?> list) {
2 for (Object o : list) {
3 System.out.println(o);
4 }
5}
  • 上界通配符 <? extends T>
    • 表示T或T的子类型
    • 主要用于从集合中读取数据
java
1public void drawShapes(List<? extends Shape> shapes) {
2 for (Shape s : shapes) {
3 s.draw(); // 安全,因为所有元素至少是Shape
4 }
5 // shapes.add(new Circle()); // 编译错误,不能添加元素
6}
  • 下界通配符 <? super T>
    • 表示T或T的超类型
    • 主要用于向集合中写入数据
java
1public void addRectangles(List<? super Rectangle> shapes) {
2 shapes.add(new Rectangle()); // 安全,因为列表接受Rectangle或其超类
3 shapes.add(new Square()); // 安全,Square是Rectangle的子类
4 // Rectangle r = shapes.get(0); // 编译错误,不能保证读取类型
5}

4. PECS原则(Producer Extends Consumer Super)

  • 如果你从集合中读取数据(Producer),使用extends
  • 如果你向集合中写入数据(Consumer),使用super
java
1// 生产者场景 - 读取数据
2public void copyElements(List<? extends E> src, List<? super E> dest) {
3 for (E e : src) { // src是生产者
4 dest.add(e); // dest是消费者
5 }
6}

5. 类型擦除

  • Java泛型在编译后会进行类型擦除
  • 泛型信息不会保留到运行时
  • 原始类型替换泛型类型参数

擦除前:

java
1List<String> list = new ArrayList<String>();
2list.add("Hello");
3String s = list.get(0);

擦除后(概念上):

java
1List list = new ArrayList();
2list.add("Hello");
3String s = (String) list.get(0);

6. 泛型的限制

  • 不能使用基本类型作为泛型类型参数
  • 不能创建泛型类型的实例:new T()
  • 不能创建泛型数组:new T[]
  • 不能对泛型类型使用instanceof
  • 静态字段不能使用泛型类的类型参数
  • 异常类不能是泛型的

7. 解决泛型限制的方法

  • 使用类型标记(Class)解决创建实例问题:
java
1public <T> T createInstance(Class<T> clazz) throws Exception {
2 return clazz.getDeclaredConstructor().newInstance();
3}
  • 使用通配符和反射创建泛型数组:
java
1public <T> T[] createArray(Class<T> clazz, int size) {
2 @SuppressWarnings("unchecked")
3 T[] array = (T[]) Array.newInstance(clazz, size);
4 return array;
5}

8. 泛型最佳实践

  1. 尽量指定具体类型参数

    • 优先使用List<String>而非原始类型List
    • 提高代码可读性和类型安全性
  2. 合理使用通配符

    • 应用PECS原则
    • 集合只读时使用? extends T
    • 集合只写时使用? super T
  3. 不要忽略编译器警告

    • 解决警告而非抑制
    • 必要时使用@SuppressWarnings,但要添加注释说明
  4. 优先考虑泛型方法

    • 单个方法需要泛型时,使用泛型方法而非泛型类
    • 增强方法的适用性
  5. 明确设计泛型边界

    • 使用有界泛型增加API的表达力
    • 例如<T extends Comparable<T>>

9. 泛型和集合框架

  • Java集合框架广泛使用泛型
  • 提供类型安全的容器
  • 避免手动类型转换

10. 泛型在框架中的应用

  • Spring的依赖注入
  • Hibernate的查询API
  • Jackson的JSON处理
  • Guava的各种工具类

26. 什么是不可变类?如何创建一个不可变类?

**不可变类(Immutable Class)**是一种一旦创建,其状态(即对象的数据)就不能被修改的类。String、Integer等包装类以及BigDecimal、BigInteger都是Java中的不可变类。

不可变类的特点

  1. 创建后状态不能改变
  2. 所有字段都是final的
  3. 安全地用于多线程环境,无需同步
  4. 可以缓存实例,节省内存(如Integer的缓存)

创建不可变类的规则

  1. 类声明为final:防止创建子类破坏不可变性

  2. 所有字段声明为private和final:防止直接访问和修改字段

  3. 不提供修改字段的方法:不提供setter方法

  4. 确保可变成员变量的安全

    • 不直接返回对可变对象的引用
    • 不存储传入的可变对象引用,而是创建副本
  5. 构造函数中创建可变对象的副本

示例代码

java
1public final class ImmutablePerson {
2 private final String name;
3 private final int age;
4 private final List<String> hobbies;
5
6 public ImmutablePerson(String name, int age, List<String> hobbies) {
7 this.name = name;
8 this.age = age;
9
10 // 创建可变对象的副本,而不是存储原始引用
11 this.hobbies = new ArrayList<>(hobbies);
12 }
13
14 public String getName() {
15 return name;
16 }
17
18 public int getAge() {
19 return age;
20 }
21
22 public List<String> getHobbies() {
23 // 返回防御性副本,而不是原始引用
24 return new ArrayList<>(hobbies);
25 }
26
27 // 提供修改方法,但返回新的实例,而不是修改现有实例
28 public ImmutablePerson withName(String newName) {
29 return new ImmutablePerson(newName, this.age, this.hobbies);
30 }
31
32 public ImmutablePerson withAge(int newAge) {
33 return new ImmutablePerson(this.name, newAge, this.hobbies);
34 }
35
36 public ImmutablePerson withAddedHobby(String hobby) {
37 List<String> newHobbies = new ArrayList<>(this.hobbies);
38 newHobbies.add(hobby);
39 return new ImmutablePerson(this.name, this.age, newHobbies);
40 }
41}

不可变类的优点

  1. 线程安全:无需同步,可以安全地在多线程间共享
  2. 简化开发:无需防御性复制,不用担心状态变化
  3. 可缓存:可以安全地重用实例,提高性能
  4. 可作为Map键或Set元素:状态不变,hashCode不变,确保集合正确性

不可变类的缺点

  1. 每次修改都创建新对象:可能增加内存消耗和垃圾回收压力
  2. 需要多个类表示一个概念:需要配套的Builder类以支持方便构建

最佳实践

  • 除非必要,优先考虑使用不可变类
  • 使用Builder模式简化复杂不可变对象的创建
  • 通过工厂方法提供常用实例,减少对象创建

27. Java中SPI机制是什么?有哪些应用场景?

**SPI(Service Provider Interface)**是Java提供的一种服务发现机制,允许第三方为某个接口提供实现。

SPI的核心思想:当服务提供者提供了一种接口的实现,服务消费者可以通过查找发现并使用该服务,而不需要了解具体实现细节。

SPI的工作原理

  1. 定义服务接口
  2. 提供该接口的实现
  3. 在实现类的META-INF/services目录下创建以接口全限定名为文件名的文件
  4. 在文件中写入实现类的全限定名
  5. 使用ServiceLoader加载实现类

SPI实现步骤示例

  1. 定义接口
java
1package com.example.spi;
2
3public interface MessageService {
4 String getMessage();
5}
  1. 提供实现类
java
1package com.example.spi.impl;
2
3import com.example.spi.MessageService;
4
5public class EmailMessageService implements MessageService {
6 @Override
7 public String getMessage() {
8 return "Email Message";
9 }
10}
11
12public class SMSMessageService implements MessageService {
13 @Override
14 public String getMessage() {
15 return "SMS Message";
16 }
17}
  1. 创建配置文件: 在META-INF/services/com.example.spi.MessageService文件中:
1com.example.spi.impl.EmailMessageService
2com.example.spi.impl.SMSMessageService
  1. 使用SPI加载服务
java
1ServiceLoader<MessageService> serviceLoader = ServiceLoader.load(MessageService.class);
2for (MessageService service : serviceLoader) {
3 System.out.println(service.getMessage());
4}

Java SPI的应用场景

  1. JDBC驱动加载

    • Java通过SPI机制加载不同数据库的驱动
    • 应用程序只需调用DriverManager.getConnection(url)
    • 具体驱动由各数据库厂商实现
  2. 日志框架

    • SLF4J使用SPI发现日志实现
    • 应用代码使用SLF4J API
    • 具体实现可以是Log4j、Logback等
  3. JavaEE中的组件发现

    • Servlet容器
    • JPA提供者
    • JAXB实现
  4. Spring中的应用

    • Spring Boot自动配置
    • Spring的SpringFactoriesLoader是SPI的一种变体
  5. Dubbo的扩展机制

    • Dubbo基于SPI设计了自己的扩展点加载机制
    • 支持IOC和AOP特性

SPI的优缺点

优点:

  1. 松耦合:服务调用方与提供方解耦
  2. 可扩展性:系统可以方便地引入新的实现
  3. 动态加载:运行时发现服务实现

缺点:

  1. 延迟加载:ServiceLoader加载所有实现,可能影响启动性能
  2. 并发不安全:ServiceLoader非线程安全
  3. 异常处理机制不完善:难以定位具体错误
  4. 功能简单:缺乏IoC、AOP等高级特性

SPI与API的区别

  • API(Application Programming Interface):服务提供方定义接口,供调用方使用
  • SPI(Service Provider Interface):调用方定义接口,供服务提供方实现

最佳实践

  • 框架设计时,考虑使用SPI增强扩展性
  • 在META-INF/services目录使用UTF-8编码的配置文件
  • 在服务提供方模块的pom.xml中配置打包时包含META-INF目录
  • 考虑使用增强的SPI框架,如Dubbo的扩展加载机制

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

Java提供了四种引用类型,它们在对象生命周期管理和内存回收方面有不同的特性。这些引用类型从强到弱依次是:强引用、软引用、弱引用和虚引用。

1. 强引用(Strong Reference)

  • 特点:最常见的引用类型,如Object obj = new Object()
  • GC行为:只要对象可达(被强引用),垃圾回收器永远不会回收它
  • 使用场景:常规对象引用
  • 生命周期:直到引用变量被销毁或显式设置为null
java
1// 创建强引用
2Object strongRef = new Object();
3// 销毁引用
4strongRef = null; // 对象可能被GC回收

2. 软引用(Soft Reference)

  • 特点:表示有用但非必需的对象
  • GC行为:仅在内存不足时被回收
  • 使用场景:实现内存敏感的缓存,当内存不足时自动释放缓存
  • 创建方式:使用java.lang.ref.SoftReference
java
1// 创建软引用
2Object obj = new Object();
3SoftReference<Object> softRef = new SoftReference<>(obj);
4obj = null; // 原始强引用置为null
5
6// 使用软引用
7Object retrievedObj = softRef.get(); // 可能返回null,如果对象已被GC回收
8if (retrievedObj != null) {
9 // 使用对象
10}

3. 弱引用(Weak Reference)

  • 特点:比软引用更弱,表示非必需的对象
  • GC行为:下一次垃圾回收时无条件回收
  • 使用场景:规范化映射(canonical mapping),防止内存泄漏
  • 创建方式:使用java.lang.ref.WeakReference
java
1// 创建弱引用
2Object obj = new Object();
3WeakReference<Object> weakRef = new WeakReference<>(obj);
4obj = null; // 原始强引用置为null
5
6// 使用弱引用
7Object retrievedObj = weakRef.get(); // 可能返回null
8if (retrievedObj != null) {
9 // 使用对象
10}

4. 虚引用(Phantom Reference)

  • 特点:最弱的引用类型,不能通过它访问对象
  • GC行为:对象被回收时,引用会被加入引用队列
  • 使用场景:跟踪对象的回收状态,执行清理操作
  • 创建方式:使用java.lang.ref.PhantomReference类,必须提供引用队列
java
1// 创建虚引用
2Object obj = new Object();
3ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
4PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue);
5obj = null; // 原始强引用置为null
6
7// 无法通过虚引用获取对象
8Object retrievedObj = phantomRef.get(); // 总是返回null
9
10// 检查引用是否入队(对象是否已回收)
11Reference<?> polledRef = refQueue.poll();
12if (polledRef == phantomRef) {
13 // 对象已被回收,执行清理操作
14}

5. 引用队列(Reference Queue)

  • 与软引用、弱引用和虚引用一起使用
  • 当引用的对象被垃圾回收器回收时,引用对象会被添加到引用队列中
  • 可用于在对象回收后执行后续操作
java
1ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
2WeakReference<Object> weakRef = new WeakReference<>(new Object(), refQueue);
3
4// 检查引用队列
5Reference<?> polledRef = refQueue.poll();
6if (polledRef != null) {
7 // 对象已被回收,处理引用
8}

6. 各引用类型的应用场景

  1. 强引用:日常编程中的默认引用

  2. 软引用

    • 实现内存敏感的缓存
    • 图片缓存
    • 大对象缓存
  3. 弱引用

    • 实现WeakHashMap,键不再使用时自动删除条目
    • 规范化映射,一个对象对应唯一实例
    • 观察者模式中的监听器列表,防止内存泄漏
  4. 虚引用

    • 跟踪对象回收状态
    • 管理直接内存资源
    • 实现清理/终结机制

7. 不同引用类型的对比

引用类型回收时机获取对象引用队列主要用途
强引用从不直接访问不适用常规对象引用
软引用内存不足时通过get()方法可选内存敏感的缓存
弱引用下次GC时通过get()方法可选规范化映射
虚引用下次GC时不能获取必需资源清理

8. 最佳实践

  • 在缓存实现中,根据缓存重要性选择软引用或弱引用
  • 处理大型数据时,考虑使用软引用减少OOM风险
  • 实现观察者模式时,使用弱引用避免内存泄漏
  • 需要跟踪对象生命周期时,考虑使用虚引用和引用队列
  • 使用引用队列时,通常需要单独的清理线程处理回收的引用

29. ConcurrentHashMap 在 Java 7 和 Java 8 中的实现有何不同?

ConcurrentHashMap是Java中线程安全的HashMap实现,在Java 7和Java 8中有着显著的实现差异。

Java 7中的ConcurrentHashMap

1. 基本结构

  • 采用**分段锁(Segment)**设计
  • 继承自ReentrantLock的Segment数组,默认16个
  • 每个Segment内部是一个小型的哈希表(HashEntry数组)

2. 工作原理

  • 对整个Map进行分段,每个Segment独立加锁
  • 允许多个线程同时操作不同段的数据
  • 理论上支持Segment数量的并发度

3. 关键特性

  • 锁粒度:Segment级别,默认16个锁
  • 并发度:取决于Segment数量,默认16
  • 内部结构:二级哈希表(Segment → HashEntry)
  • 扩容机制:每个Segment独立扩容,不影响其他Segment

4. 代码示例

java
1// Java 7中的ConcurrentHashMap内部结构(简化版)
2static final class Segment<K,V> extends ReentrantLock implements Serializable {
3 transient volatile HashEntry<K,V>[] table;
4 transient int count;
5 // ...
6}
7
8static final class HashEntry<K,V> {
9 final int hash;
10 final K key;
11 volatile V value;
12 volatile HashEntry<K,V> next;
13}

Java 8中的ConcurrentHashMap

1. 基本结构

  • 摒弃了分段锁设计
  • 采用Node数组+链表+红黑树结构
  • 类似于HashMap,但所有操作都保证线程安全

2. 工作原理

  • 使用CAS(Compare and Swap)操作和synchronized关键字
  • CAS用于操作Node数组,synchronized用于链表/红黑树操作
  • 基于Node的hash值锁定,更细粒度的锁定

3. 关键特性

  • 锁粒度:桶(bucket)级别,比Segment更细
  • 并发度:理论上等于桶数量
  • 内部结构:数组+链表+红黑树(链表长度>8时转为红黑树)
  • 扩容机制:支持并发扩容,多线程协作完成

4. 代码示例

java
1// Java 8中的ConcurrentHashMap内部结构(简化版)
2static final class Node<K,V> implements Map.Entry<K,V> {
3 final int hash;
4 final K key;
5 volatile V val;
6 volatile Node<K,V> next;
7}
8
9// 红黑树节点
10static final class TreeNode<K,V> extends Node<K,V> {
11 TreeNode<K,V> parent;
12 TreeNode<K,V> left;
13 TreeNode<K,V> right;
14 TreeNode<K,V> prev;
15 boolean red;
16}

主要区别对比

特性Java 7 ConcurrentHashMapJava 8 ConcurrentHashMap
锁机制分段锁(Segment)synchronized + CAS
锁粒度Segment级别(较粗)桶级别(更细)
数据结构Segment数组 + HashEntry数组Node数组 + 链表/红黑树
树化不支持链表长度>8时转为红黑树
并发度取决于Segment数量,固定理论上等于桶数量,更高
扩容机制Segment独立扩容并发扩容,多线程协作
内存占用较高(Segment对象开销)较低(无Segment开销)
查询性能较好更好(受益于红黑树)
迭代器弱一致性弱一致性

核心API操作对比

1. put操作

  • Java 7:获取Segment锁,然后在Segment内部操作
  • Java 8:CAS尝试直接插入,失败则对桶加synchronized锁

2. get操作

  • Java 7:不加锁,通过volatile保证可见性
  • Java 8:不加锁,通过volatile保证可见性

3. size操作

  • Java 7:先尝试不加锁统计,失败后锁住所有Segment
  • Java 8:使用CounterCell数组,分散计数,提高并发性

4. 扩容

  • Java 7:单线程扩容Segment内部的HashEntry数组
  • Java 8:多线程协作扩容,每个线程处理一部分桶

性能考虑

  1. Java 8优势

    • 锁粒度更细,理论上并发度更高
    • 红黑树提高了大容量场景下的查询性能
    • 多线程协作扩容,提高扩容效率
  2. 适用场景

    • 读操作远多于写操作:两个版本都很好
    • 写操作频繁:Java 8优势更明显
    • 大数据量、高并发:Java 8性能更好

30. Java内存模型(JMM)是什么?它有哪些特性?

**Java内存模型(Java Memory Model,JMM)**是Java虚拟机规范中定义的一种抽象模型,它规定了Java程序中各种变量的访问规则,以及在多线程环境中如何处理共享变量的可见性、原子性和有序性问题。

1. JMM的核心目标

  • 定义Java多线程程序中变量的访问规则
  • 屏蔽各种硬件和操作系统的内存访问差异
  • 提供一致的内存可见性保证
  • 规范多线程环境下指令重排序的影响

2. Java内存模型的基本结构

JMM将内存分为主内存工作内存两部分:

  • 主内存(Main Memory)

    • 所有线程共享
    • 存储Java对象的实例数据
    • 包含原始变量(如int、long等)以及引用变量的引用值
  • 工作内存(Working Memory)

    • 线程私有,类似于CPU缓存
    • 存储线程需要的变量的副本
    • 线程不能直接访问主内存中的变量

3. JMM的核心特性

3.1 原子性(Atomicity)

  • 基本保证:基本类型的读写操作是原子的(long和double除外)
  • 增强保证:通过synchronized和Lock等实现复合操作的原子性
  • 相关API:java.util.concurrent.atomic包中的原子类
java
1// 原子变量示例
2AtomicInteger counter = new AtomicInteger(0);
3counter.incrementAndGet(); // 原子操作

3.2 可见性(Visibility)

  • 一个线程对共享变量的修改能够及时被其他线程看到
  • 实现方式:volatile、synchronized、final关键字
  • 可见性不保证原子性
java
1// volatile保证可见性
2private volatile boolean flag = false;
3
4// 线程A
5flag = true; // 修改对其他线程立即可见
6
7// 线程B
8while (!flag) {
9 // 能够感知flag的变化
10}

3.3 有序性(Ordering)

  • 程序按代码顺序执行
  • JVM为优化性能可能进行指令重排序
  • 重排序不会改变单线程执行结果,但会影响多线程执行结果
  • 通过volatile、synchronized、Lock等保证有序性
java
1// 重排序示例
2int a = 0;
3boolean flag = false;
4
5// 可能的重排序
6flag = true;
7a = 1;
8
9// 通过volatile防止重排序
10private volatile int a = 0;
11private volatile boolean flag = false;

4. Happens-Before原则

JMM定义了多种Happens-Before规则,保证线程间的操作可见性:

  1. 程序顺序规则:单线程内按代码顺序执行
  2. 监视器锁规则:释放锁操作happens-before于随后对同一锁的获取
  3. volatile变量规则:写volatile变量happens-before于随后对这个变量的读
  4. 线程启动规则:启动线程的操作happens-before于被启动线程的任何操作
  5. 线程终止规则:线程中的所有操作happens-before于其他线程感知到该线程结束
  6. 中断规则:调用线程的interrupt()方法happens-before于被中断线程检测到中断事件
  7. 终结器规则:对象的构造函数执行结束happens-before于终结器(finalize)方法
  8. 传递性:如果A happens-before B,且B happens-before C,则A happens-before C

5. 内存屏障(Memory Barriers)

JMM使用内存屏障来禁止特定类型的处理器重排序:

  • LoadLoad屏障:确保Load1先于Load2完成
  • StoreStore屏障:确保Store1先于Store2完成
  • LoadStore屏障:确保Load1先于Store2完成
  • StoreLoad屏障:确保Store1先于Load2完成(全能屏障)

6. volatile关键字的内存语义

  • 可见性:保证变量的修改对所有线程立即可见
  • 有序性:禁止指令重排序
  • 实现机制
    • 写volatile变量时,会插入StoreStore和StoreLoad屏障
    • 读volatile变量时,会插入LoadLoad和LoadStore屏障
java
1// volatile变量使用场景
2public class SafePublication {
3 private volatile Object instance;
4
5 public void publish() {
6 instance = new Object(); // 写volatile变量
7 }
8
9 public Object getInstance() {
10 return instance; // 读volatile变量
11 }
12}

7. final关键字的内存语义

  • 保证final字段在构造函数完成时被正确初始化
  • JMM会禁止final字段的初始化被重排序到构造函数之外
  • 保证其他线程看到的final字段值都是初始化后的值

8. synchronized的内存语义

  • 获取锁:会清空工作内存,从主内存重新加载共享变量
  • 释放锁:会将工作内存中的修改刷新到主内存
  • 提供了互斥性和可见性保证

9. JMM对并发编程的影响

  1. 单例模式实现:双重检查锁定需要volatile修饰实例变量
java
1public class Singleton {
2 private static volatile Singleton instance;
3
4 private Singleton() {}
5
6 public static Singleton getInstance() {
7 if (instance == null) {
8 synchronized (Singleton.class) {
9 if (instance == null) {
10 instance = new Singleton();
11 }
12 }
13 }
14 return instance;
15 }
16}

44. Java注解是什么?如何自定义注解及其应用场景?

**注解(Annotations)**是Java 5引入的一种特殊类型的标记,可以应用于类、方法、字段、参数和其他程序元素。它们提供了一种声明式的编程方式,使开发者能够向代码中添加元数据信息,这些信息可以被编译器、运行时环境或第三方工具使用。

1. 注解的基础概念

1.1 注解的定义

注解以@interface关键字定义:

java
1public @interface MyAnnotation {
2 String value() default "defaultValue";
3 int count() default 0;
4 boolean enabled() default true;
5}

1.2 元注解

元注解是用于注解其他注解的注解,Java提供了几个标准的元注解:

  • @Retention:指定注解的保留策略

    • SOURCE:只在源码中保留,编译时丢弃
    • CLASS:保留在编译后的类文件中,但运行时不可用(默认)
    • RUNTIME:保留在运行时,可以通过反射访问
  • @Target:指定注解可以应用于哪些程序元素

    • TYPE:类、接口、枚举
    • FIELD:字段
    • METHOD:方法
    • PARAMETER:方法参数
    • CONSTRUCTOR:构造方法
    • LOCAL_VARIABLE:局部变量
    • ANNOTATION_TYPE:注解类型
    • PACKAGE:包
    • TYPE_PARAMETER:Java 8+,类型参数
    • TYPE_USE:Java 8+,类型使用
  • @Documented:指定该注解是否包含在JavaDoc文档中

  • @Inherited:指定该注解可以被子类继承

  • @Repeatable:Java 8+,指定该注解可以在同一程序元素上多次使用

1.3 常见的内置注解

Java提供了一些内置的注解:

  • @Override:表示方法重写父类方法
  • @Deprecated:表示元素已过时
  • @SuppressWarnings:抑制编译器警告
  • @FunctionalInterface:Java 8+,表示函数式接口
  • @SafeVarargs:Java 7+,抑制可变参数相关的警告
  • @Native:Java 8+,表示常量可以被本地代码引用

2. 自定义注解

2.1 创建自定义注解

java
1// 自定义运行时注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target({ElementType.TYPE, ElementType.METHOD})
4public @interface Authorized {
5 String[] roles() default {"USER"};
6 boolean guestAllowed() default false;
7}
8
9// 自定义编译时注解
10@Retention(RetentionPolicy.SOURCE)
11@Target(ElementType.METHOD)
12public @interface MethodInfo {
13 String author() default "unknown";
14 String date();
15 String comment() default "";
16}

2.2 自定义可重复注解

java
1// 定义容器注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.METHOD)
4public @interface Schedules {
5 Schedule[] value();
6}
7
8// 定义可重复的注解
9@Retention(RetentionPolicy.RUNTIME)
10@Target(ElementType.METHOD)
11@Repeatable(Schedules.class)
12public @interface Schedule {
13 String cron();
14 String description() default "";
15}
16
17// 使用可重复注解
18@Schedule(cron = "0 0 12 * * ?", description = "Daily job")
19@Schedule(cron = "0 0 0 * * ?", description = "Nightly job")
20public void periodicJob() {
21 // 方法实现
22}

2.3 注解的使用方式

java
1// 应用在类上
2@Authorized(roles = {"ADMIN", "MANAGER"})
3public class AdminController {
4
5 // 应用在方法上
6 @Authorized(roles = {"ADMIN"})
7 public void deleteUser(int userId) {
8 // 方法实现
9 }
10
11 // 应用在方法上,使用默认值
12 @Authorized
13 public void viewUsers() {
14 // 方法实现
15 }
16}
17
18// 编译时注解应用
19@MethodInfo(author = "John", date = "2023-10-15", comment = "Initial implementation")
20public void processData() {
21 // 方法实现
22}

3. 注解处理方式

注解本身不会做任何事情,它们需要被处理才能发挥作用。处理注解的方式主要有两种:编译时处理和运行时处理。

3.1 运行时处理(通过反射API)

java
1// 运行时处理注解示例
2public class AuthorizationHandler {
3 public static boolean isAuthorized(Object instance, String methodName, String[] userRoles) {
4 try {
5 Class<?> clazz = instance.getClass();
6
7 // 检查类级别的授权
8 if (clazz.isAnnotationPresent(Authorized.class)) {
9 Authorized auth = clazz.getAnnotation(Authorized.class);
10 if (!hasCommonElement(auth.roles(), userRoles) && !auth.guestAllowed()) {
11 return false;
12 }
13 }
14
15 // 检查方法级别的授权
16 Method method = clazz.getMethod(methodName);
17 if (method.isAnnotationPresent(Authorized.class)) {
18 Authorized auth = method.getAnnotation(Authorized.class);
19 return hasCommonElement(auth.roles(), userRoles) || auth.guestAllowed();
20 }
21
22 return true; // 如果没有注解,默认授权
23 } catch (NoSuchMethodException e) {
24 return false; // 方法不存在
25 }
26 }
27
28 private static boolean hasCommonElement(String[] array1, String[] array2) {
29 for (String str1 : array1) {
30 for (String str2 : array2) {
31 if (str1.equals(str2)) {
32 return true;
33 }
34 }
35 }
36 return false;
37 }
38}
39
40// 使用处理器
41public void executeMethod(Object controller, String methodName) {
42 String[] userRoles = getCurrentUserRoles(); // 获取当前用户角色
43 if (AuthorizationHandler.isAuthorized(controller, methodName, userRoles)) {
44 // 调用方法
45 // ...
46 } else {
47 throw new SecurityException("Unauthorized access");
48 }
49}

3.2 编译时处理(通过Annotation Processing Tool)

使用Java的注解处理器API,可以在编译时处理注解:

java
1@SupportedAnnotationTypes("com.example.MethodInfo")
2@SupportedSourceVersion(SourceVersion.RELEASE_8)
3public class MethodInfoProcessor extends AbstractProcessor {
4
5 @Override
6 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
7 for (TypeElement annotation : annotations) {
8 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
9
10 for (Element element : elements) {
11 if (element.getKind() == ElementKind.METHOD) {
12 MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);
13
14 // 获取注解信息
15 String author = methodInfo.author();
16 String date = methodInfo.date();
17 String comment = methodInfo.comment();
18
19 // 处理注解,例如生成文档或者验证
20 processingEnv.getMessager().printMessage(
21 Diagnostic.Kind.NOTE,
22 "Method: " + element.getSimpleName() +
23 ", Author: " + author +
24 ", Date: " + date +
25 ", Comment: " + comment,
26 element
27 );
28 }
29 }
30 }
31
32 return true;
33 }
34}

要使用这个注解处理器,需要:

  1. 将处理器打包成JAR文件
  2. META-INF/services/javax.annotation.processing.Processor文件中注册
  3. 在编译时包含该JAR文件

4. 注解的应用场景

4.1 框架配置和元数据

例如Spring框架中的注解用于配置:

java
1@Controller
2@RequestMapping("/users")
3public class UserController {
4 @Autowired
5 private UserService userService;
6
7 @GetMapping("/{id}")
8 public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
9 // 实现
10 }
11}

4.2 编译时检查和代码生成

例如Lombok库使用注解来生成代码:

java
1@Data // 自动生成getter、setter、equals、hashCode、toString
2@Builder // 生成建造者模式代码
3public class User {
4 private Long id;
5 private String username;
6 private String email;
7}

4.3 运行时行为修改

例如在Web应用中进行权限检查:

java
1@RequiresRole("admin")
2public void deleteUser(Long userId) {
3 // 实现
4}

4.4 文档生成

例如Swagger/OpenAPI注解用于生成API文档:

java
1@ApiOperation(value = "Get user by ID", response = User.class)
2@GetMapping("/{id}")
3public ResponseEntity<User> getUser(@PathVariable @ApiParam(value = "User ID") Long id) {
4 // 实现
5}

4.5 单元测试

JUnit框架中使用注解标记测试方法:

java
1@Test
2public void testUserRegistration() {
3 // 测试逻辑
4}
5
6@Before
7public void setup() {
8 // 设置测试环境
9}
10
11@After
12public void cleanup() {
13 // 清理测试环境
14}

4.6 依赖注入和控制反转

例如在Spring框架中:

java
1@Component
2public class UserServiceImpl implements UserService {
3 @Autowired
4 private UserRepository userRepository;
5
6 // 实现方法
7}

5. 实际应用案例

5.1 自定义验证注解

java
1// 定义验证注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.FIELD)
4public @interface ValidateEmail {
5 String message() default "Invalid email format";
6 boolean nullable() default false;
7}
8
9// 定义处理器
10public class ValidationProcessor {
11 public List<String> validate(Object object) {
12 List<String> errors = new ArrayList<>();
13 Class<?> clazz = object.getClass();
14
15 for (Field field : clazz.getDeclaredFields()) {
16 if (field.isAnnotationPresent(ValidateEmail.class)) {
17 ValidateEmail annotation = field.getAnnotation(ValidateEmail.class);
18 field.setAccessible(true);
19
20 try {
21 String email = (String) field.get(object);
22 if (email == null) {
23 if (!annotation.nullable()) {
24 errors.add(field.getName() + ": Email cannot be null");
25 }
26 } else if (!isValidEmail(email)) {
27 errors.add(field.getName() + ": " + annotation.message());
28 }
29 } catch (IllegalAccessException e) {
30 errors.add("Cannot access field: " + field.getName());
31 }
32 }
33 }
34
35 return errors;
36 }
37
38 private boolean isValidEmail(String email) {
39 // 简单的邮箱验证逻辑
40 return email.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
41 }
42}
43
44// 使用验证注解
45public class User {
46 private Long id;
47 private String name;
48
49 @ValidateEmail(message = "Please provide a valid email address")
50 private String email;
51
52 // 构造方法、getter和setter
53}
54
55// 验证对象
56User user = new User(1L, "John Doe", "invalid-email");
57ValidationProcessor validator = new ValidationProcessor();
58List<String> errors = validator.validate(user);
59if (!errors.isEmpty()) {
60 for (String error : errors) {
61 System.out.println("Validation error: " + error);
62 }
63}

5.2 ORM映射注解

java
1// 表注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.TYPE)
4public @interface Table {
5 String name();
6}
7
8// 列注解
9@Retention(RetentionPolicy.RUNTIME)
10@Target(ElementType.FIELD)
11public @interface Column {
12 String name() default "";
13 boolean primary() default false;
14}
15
16// 实体类
17@Table(name = "users")
18public class User {
19 @Column(name = "id", primary = true)
20 private Long id;
21
22 @Column(name = "username")
23 private String username;
24
25 @Column(name = "email")
26 private String email;
27
28 // 构造方法、getter和setter
29}
30
31// ORM处理器
32public class OrmMapper {
33 public String generateInsertSql(Object entity) {
34 Class<?> clazz = entity.getClass();
35 if (!clazz.isAnnotationPresent(Table.class)) {
36 throw new IllegalArgumentException("Class must be annotated with @Table");
37 }
38
39 Table tableAnnotation = clazz.getAnnotation(Table.class);
40 String tableName = tableAnnotation.name();
41
42 List<String> columnNames = new ArrayList<>();
43 List<String> paramPlaceholders = new ArrayList<>();
44 List<Object> values = new ArrayList<>();
45
46 try {
47 for (Field field : clazz.getDeclaredFields()) {
48 if (field.isAnnotationPresent(Column.class)) {
49 field.setAccessible(true);
50 Column column = field.getAnnotation(Column.class);
51
52 // 跳过自增主键
53 if (column.primary() && field.get(entity) == null) {
54 continue;
55 }
56
57 String columnName = column.name().isEmpty() ?
58 field.getName() : column.name();
59
60 columnNames.add(columnName);
61 paramPlaceholders.add("?");
62 values.add(field.get(entity));
63 }
64 }
65 } catch (IllegalAccessException e) {
66 throw new RuntimeException("Error accessing fields", e);
67 }
68
69 StringBuilder sql = new StringBuilder();
70 sql.append("INSERT INTO ")
71 .append(tableName)
72 .append(" (")
73 .append(String.join(", ", columnNames))
74 .append(") VALUES (")
75 .append(String.join(", ", paramPlaceholders))
76 .append(")");
77
78 return sql.toString();
79 // 实际应用中还需要一个方法来返回values列表用于设置PreparedStatement参数
80 }
81}

6. 注解的最佳实践

6.1 命名和组织

  • 使用清晰、描述性的名称
  • 将相关注解组织在同一个包中
  • 遵循Java命名约定,使用驼峰式命名

6.2 文档和注释

  • 为每个注解提供清晰的JavaDoc
  • 解释注解的目的、用法和限制
  • 提供使用示例

6.3 设计考虑

  • 确保注解定义简单明了
  • 提供合理的默认值
  • 避免过度使用注解
  • 考虑注解的组合使用
  • 注解应该是声明性的,不应包含业务逻辑

6.4 处理器设计

  • 使注解处理器可重用
  • 提供明确的错误信息
  • 确保处理器的线程安全性
  • 缓存反射结果以提高性能

6.5 安全考虑

  • 验证注解的使用是否符合预期
  • 防止恶意代码利用注解处理器
  • 考虑注解在反射中的安全影响

7. Java注解与其他技术的比较

7.1 注解 vs XML配置

优点

  • 类型安全,编译时检查
  • 与代码放在一起,易于维护
  • 代码更简洁

缺点

  • 修改需要重新编译
  • 不适合频繁变化的配置
  • 对代码有侵入性

7.2 注解 vs 代码约定

优点

  • 更明确的语义
  • IDE支持更好
  • 可以包含元数据

缺点

  • 引入了额外的概念
  • 可能使简单的事情复杂化

8. Java注解的局限性

  • 只能包含常量值,不能包含代码块
  • 不能直接继承其他注解
  • 处理注解需要额外的代码
  • 过度使用会导致代码可读性下降
  • 依赖反射可能影响性能

9. 未来发展趋势

  • 更强大的编译时注解处理
  • 与模块系统的更好集成
  • 更丰富的元注解
  • 改进的泛型和注解的结合使用
  1. 无锁并发编程:依赖于对JMM的深入理解
  2. 线程池优化:合理设置工作线程数,避免过多的上下文切换
  3. 避免伪共享:对齐内存以提高缓存效率

10. JMM与硬件内存架构的关系

  • JMM是一个抽象模型,与具体硬件无关
  • JVM通过内存屏障等机制将JMM映射到不同硬件架构
  • 屏蔽了不同平台内存模型的差异,实现"一次编写,到处运行"

11. JDK 9及以后的改进

  • 增强了volatile的语义
  • 改进了final字段的初始化保证
  • 引入VarHandle API,提供更细粒度的内存访问控制

最佳实践

  1. 优先使用高级并发工具(java.util.concurrent包)
  2. 正确使用synchronized、volatile和final
  3. 避免复杂的无锁算法,除非有性能瓶颈
  4. 理解内存可见性问题,避免常见并发陷阱
  5. 编写线程安全代码时,考虑原子性、可见性和有序性三个维度

31. Java 8中的Stream API有什么特点?如何使用?

Stream API是Java 8引入的一种处理集合的方式,它提供了一种高效且易于使用的处理数据的方法,支持函数式编程风格的操作。

1. Stream API的核心特点

  • 函数式编程:使用Lambda表达式和方法引用进行操作
  • 声明式编程:关注"做什么"而非"怎么做"
  • 流水线操作:支持中间操作和终端操作链式调用
  • 延迟执行:中间操作不会立即执行,直到遇到终端操作
  • 内部迭代:由Stream API控制迭代,而非由用户代码显式控制

2. Stream的创建方式

java
1// 从集合创建
2List<String> list = Arrays.asList("a", "b", "c");
3Stream<String> stream1 = list.stream();
4Stream<String> parallelStream = list.parallelStream(); // 并行流
5
6// 从数组创建
7String[] array = {"a", "b", "c"};
8Stream<String> stream2 = Arrays.stream(array);
9
10// Stream.of静态方法
11Stream<String> stream3 = Stream.of("a", "b", "c");
12
13// 使用Stream.generate创建无限流
14Stream<String> stream4 = Stream.generate(() -> "element").limit(10);
15
16// 使用Stream.iterate创建无限流
17Stream<Integer> stream5 = Stream.iterate(0, n -> n + 2).limit(10);
18
19// 使用IntStream、LongStream、DoubleStream创建数值流
20IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
21LongStream longStream = LongStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5

3. Stream的操作类型

Stream API操作分为两类:中间操作(Intermediate Operations)和终端操作(Terminal Operations)。

3.1 中间操作:返回一个新的Stream,可以链式调用

  • 过滤操作

    • filter(Predicate<T>): 过滤元素
    • distinct(): 去重
    • limit(long): 限制元素数量
    • skip(long): 跳过元素
  • 映射操作

    • map(Function<T, R>): 元素转换
    • flatMap(Function<T, Stream<R>>): 压平嵌套流
    • mapToInt/Long/Double(): 转换为原始类型流
  • 排序操作

    • sorted(): 自然顺序排序
    • sorted(Comparator<T>): 指定排序方式

3.2 终端操作:触发流的计算,返回非Stream结果

  • 匹配操作

    • anyMatch(Predicate<T>): 任一元素匹配
    • allMatch(Predicate<T>): 所有元素匹配
    • noneMatch(Predicate<T>): 没有元素匹配
  • 查找操作

    • findFirst(): 返回第一个元素
    • findAny(): 返回任意元素
  • 归约操作

    • reduce(BinaryOperator<T>): 将流元素组合
    • reduce(T, BinaryOperator<T>): 指定初始值的归约
    • count(): 计数
    • max(Comparator<T>): 最大值
    • min(Comparator<T>): 最小值
  • 收集操作

    • collect(Collector<T, A, R>): 收集到容器
    • toArray(): 转为数组
  • 遍历操作

    • forEach(Consumer<T>): 对每个元素执行操作

4. Stream API的常见用例

4.1 过滤和收集

java
1List<String> filtered = persons.stream()
2 .filter(p -> p.getAge() > 18)
3 .map(Person::getName)
4 .collect(Collectors.toList());

4.2 统计操作

java
1// 计算总和
2int sum = numbers.stream()
3 .mapToInt(Integer::intValue)
4 .sum();
5
6// 计算平均值
7double average = persons.stream()
8 .mapToInt(Person::getAge)
9 .average()
10 .orElse(0);
11
12// 分组统计
13Map<Department, Long> countByDept = employees.stream()
14 .collect(Collectors.groupingBy(
15 Employee::getDepartment,
16 Collectors.counting()
17 ));

4.3 查找和匹配

java
1// 查找任意一个成年人
2Optional<Person> adult = persons.stream()
3 .filter(p -> p.getAge() >= 18)
4 .findAny();
5
6// 检查是否所有人都成年
7boolean allAdult = persons.stream()
8 .allMatch(p -> p.getAge() >= 18);

4.4 归约和汇总

java
1// 计算总和
2int total = numbers.stream()
3 .reduce(0, Integer::sum);
4
5// 连接字符串
6String joined = strings.stream()
7 .reduce("", String::concat);
8
9// 使用joining更高效连接字符串
10String joinedBetter = strings.stream()
11 .collect(Collectors.joining(", "));

4.5 分组和分区

java
1// 按部门分组
2Map<Department, List<Employee>> byDept = employees.stream()
3 .collect(Collectors.groupingBy(Employee::getDepartment));
4
5// 按成年与否分区
6Map<Boolean, List<Person>> adultPartition = persons.stream()
7 .collect(Collectors.partitioningBy(p -> p.getAge() >= 18));

5. 并行流(Parallel Stream)

Stream API提供了并行处理能力,可以利用多核处理器的优势:

java
1// 创建并行流
2Stream<String> parallelStream = list.parallelStream();
3// 或将顺序流转换为并行流
4Stream<String> parallel = stream.parallel();
5
6// 并行处理示例
7long count = persons.parallelStream()
8 .filter(p -> p.getAge() > 18)
9 .count();

并行流注意事项

  • 确保操作无状态且线程安全
  • 避免使用共享状态
  • 对于较小的数据集,顺序流可能更快
  • 某些操作(如findFirst)在并行流上效率可能更低
  • 使用有序集合时,排序操作可能抵消并行优势

6. Collectors工具类

java.util.stream.Collectors提供了多种预定义的收集器:

java
1// 收集到List
2List<String> list = stream.collect(Collectors.toList());
3
4// 收集到Set
5Set<String> set = stream.collect(Collectors.toSet());
6
7// 收集到Map
8Map<Integer, String> map = persons.stream()
9 .collect(Collectors.toMap(Person::getId, Person::getName));
10
11// 连接字符串
12String joined = stream.collect(Collectors.joining(", "));
13
14// 分组
15Map<Department, List<Employee>> byDept = employees.stream()
16 .collect(Collectors.groupingBy(Employee::getDepartment));
17
18// 嵌套分组
19Map<Department, Map<EmployeeType, List<Employee>>> byDeptAndType =
20 employees.stream()
21 .collect(Collectors.groupingBy(
22 Employee::getDepartment,
23 Collectors.groupingBy(Employee::getType)
24 ));
25
26// 计算统计信息
27DoubleSummaryStatistics stats = employees.stream()
28 .collect(Collectors.summarizingDouble(Employee::getSalary));

7. Stream API的优势和限制

优势

  • 代码简洁清晰,提高可读性
  • 支持函数式编程范式
  • 延迟执行提高效率
  • 并行处理能力
  • 内部迭代减少模板代码

限制

  • 不能修改源数据结构
  • 只能遍历一次
  • 不保证执行顺序(除非明确排序)
  • 调试复杂性增加

8. Stream API的最佳实践

  • 优先使用特定类型的流(IntStream、LongStream、DoubleStream)处理数值
  • 适当使用并行流提高性能
  • 避免在流操作中修改外部状态
  • 链式调用保持代码简洁
  • 使用方法引用代替简单的Lambda表达式
  • 对于复杂的收集操作,优先使用Collectors工具类
  • 考虑使用peek()方法进行调试

32. 什么是FunctionalInterface?Java 8中的函数式接口有哪些?

**函数式接口(FunctionalInterface)**是Java 8引入的一个重要概念,指只有一个抽象方法的接口,用于支持Lambda表达式和方法引用。

1. 函数式接口的特点

  • 只包含一个抽象方法(Single Abstract Method,简称SAM)
  • 可以包含多个默认方法和静态方法
  • 可以包含Object类中的public方法的抽象形式
  • 可以使用@FunctionalInterface注解标记(非必须但推荐)

2. @FunctionalInterface注解

@FunctionalInterface是一个标记注解,用于表示一个接口是函数式接口:

java
1@FunctionalInterface
2public interface MyFunctional {
3 void execute(); // 唯一的抽象方法
4
5 // 默认方法,不影响函数式接口的性质
6 default void defaultMethod() {
7 System.out.println("Default Method");
8 }
9
10 // 静态方法,不影响函数式接口的性质
11 static void staticMethod() {
12 System.out.println("Static Method");
13 }
14
15 // Object类的方法,不计入抽象方法数量
16 @Override
17 boolean equals(Object obj);
18}

3. Java 8内置的函数式接口

Java 8在java.util.function包中提供了多个标准的函数式接口,分为以下几类:

3.1 基础函数式接口

  1. Consumer:接收一个输入参数但不返回任何结果

    java
    1@FunctionalInterface
    2public interface Consumer<T> {
    3 void accept(T t);
    4}
    5// 使用示例
    6Consumer<String> printer = s -> System.out.println(s);
    7printer.accept("Hello World");
  2. Supplier:不接收参数但返回一个结果

    java
    1@FunctionalInterface
    2public interface Supplier<T> {
    3 T get();
    4}
    5// 使用示例
    6Supplier<String> supplier = () -> "Hello World";
    7String result = supplier.get();
  3. Function:接收一个输入参数并返回一个结果

    java
    1@FunctionalInterface
    2public interface Function<T, R> {
    3 R apply(T t);
    4}
    5// 使用示例
    6Function<String, Integer> length = s -> s.length();
    7Integer len = length.apply("Hello");
  4. Predicate:接收一个参数,返回一个布尔结果

    java
    1@FunctionalInterface
    2public interface Predicate<T> {
    3 boolean test(T t);
    4}
    5// 使用示例
    6Predicate<String> isEmpty = s -> s.isEmpty();
    7boolean empty = isEmpty.test("Hello");
  5. UnaryOperator:接收一个参数,返回相同类型的结果

    java
    1@FunctionalInterface
    2public interface UnaryOperator<T> extends Function<T, T> {
    3}
    4// 使用示例
    5UnaryOperator<String> toUpperCase = s -> s.toUpperCase();
    6String upper = toUpperCase.apply("hello");
  6. BinaryOperator:接收两个相同类型参数,返回相同类型的结果

    java
    1@FunctionalInterface
    2public interface BinaryOperator<T> extends BiFunction<T, T, T> {
    3}
    4// 使用示例
    5BinaryOperator<Integer> add = (a, b) -> a + b;
    6Integer sum = add.apply(5, 3);

3.2 特殊类型的函数式接口

  1. BiConsumer:接收两个输入参数,不返回结果

    java
    1@FunctionalInterface
    2public interface BiConsumer<T, U> {
    3 void accept(T t, U u);
    4}
    5// 使用示例
    6BiConsumer<String, Integer> printEntry = (key, value) ->
    7 System.out.println(key + ": " + value);
    8printEntry.accept("Age", 30);
  2. BiFunction:接收两个输入参数,返回一个结果

    java
    1@FunctionalInterface
    2public interface BiFunction<T, U, R> {
    3 R apply(T t, U u);
    4}
    5// 使用示例
    6BiFunction<String, String, Integer> sumLengths =
    7 (s1, s2) -> s1.length() + s2.length();
    8Integer totalLength = sumLengths.apply("Hello", "World");
  3. BiPredicate:接收两个输入参数,返回一个布尔结果

    java
    1@FunctionalInterface
    2public interface BiPredicate<T, U> {
    3 boolean test(T t, U u);
    4}
    5// 使用示例
    6BiPredicate<String, Integer> checkLength =
    7 (s, len) -> s.length() == len;
    8boolean result = checkLength.test("Hello", 5);

3.3 原始类型特化的函数式接口

为避免基本类型的装箱拆箱开销,Java 8提供了针对基本类型的特化接口:

  1. IntFunctionLongFunctionDoubleFunction: 接收一个基本类型参数,返回泛型结果

  2. ToIntFunction,*ToLongFunctionToDoubleFunction: 接收一个泛型参数,返回基本类型结果

  3. IntConsumerLongConsumerDoubleConsumer: 接收一个基本类型参数,不返回结果

  4. IntPredicateLongPredicateDoublePredicate: 接收一个基本类型参数,返回布尔结果

  5. IntSupplierLongSupplierDoubleSupplier: 不接收参数,返回基本类型结果

  6. IntUnaryOperatorLongUnaryOperatorDoubleUnaryOperator: 接收一个基本类型参数,返回相同类型结果

java
1// 使用示例
2IntFunction<String> intToString = i -> Integer.toString(i);
3String str = intToString.apply(42);
4
5IntConsumer printer = i -> System.out.println(i);
6printer.accept(42);
7
8IntPredicate isEven = i -> i % 2 == 0;
9boolean even = isEven.test(42);

4. 多个函数式接口的组合

函数式接口通常提供了组合方法,允许通过默认方法组合多个操作:

java
1// Predicate组合
2Predicate<String> isEmpty = String::isEmpty;
3Predicate<String> isNotEmpty = isEmpty.negate();
4Predicate<String> isNotEmptyAndLong = isNotEmpty.and(s -> s.length() > 10);
5
6// Function组合 (andThen和compose)
7Function<String, String> toUpperCase = String::toUpperCase;
8Function<String, String> trim = String::trim;
9Function<String, String> trimAndUpperCase = trim.andThen(toUpperCase);
10Function<String, String> upperCaseAndTrim = trim.compose(toUpperCase);
11
12// Consumer组合
13Consumer<String> logger = s -> System.out.println("Log: " + s);
14Consumer<String> printer = s -> System.out.println("Print: " + s);
15Consumer<String> logAndPrint = logger.andThen(printer);

5. 自定义函数式接口

可以根据需要定义自己的函数式接口,只需确保它只有一个抽象方法:

java
1@FunctionalInterface
2public interface TriFunction<T, U, V, R> {
3 R apply(T t, U u, V v);
4}
5
6// 使用示例
7TriFunction<String, String, String, Integer> calculateLength =
8 (s1, s2, s3) -> s1.length() + s2.length() + s3.length();
9Integer length = calculateLength.apply("Hello", "Functional", "Interface");

6. Lambda表达式和方法引用

函数式接口与Lambda表达式和方法引用密切相关:

Lambda表达式作为函数式接口的实例:

java
1Predicate<String> isEmpty = s -> s.isEmpty();
2Consumer<String> printer = s -> System.out.println(s);

方法引用作为函数式接口的实例:

java
1// 静态方法引用
2Function<String, Integer> parseInt = Integer::parseInt;
3
4// 实例方法引用(特定实例)
5Consumer<String> printer = System.out::println;
6
7// 实例方法引用(任意实例)
8Function<String, Integer> getLength = String::length;
9
10// 构造方法引用
11Supplier<List<String>> newList = ArrayList::new;
12Function<String, Integer> newInteger = Integer::new;

7. 常见应用场景

函数式接口常用于以下场景:

  1. 集合操作:Stream API操作、集合排序等

    java
    1// 列表排序
    2list.sort(Comparator.comparing(Person::getName));
    3
    4// Stream操作
    5list.stream()
    6 .filter(p -> p.getAge() > 18)
    7 .map(Person::getName)
    8 .forEach(System.out::println);
  2. 异步任务和回调

    java
    1// 线程
    2new Thread(() -> System.out.println("Running in a thread")).start();
    3
    4// 任务提交
    5ExecutorService executor = Executors.newSingleThreadExecutor();
    6executor.submit(() -> performTask());
  3. 事件处理

    java
    1button.addActionListener(e -> System.out.println("Button clicked"));
  4. 策略模式

    java
    1public void processStrings(List<String> strings, Predicate<String> filter) {
    2 for (String s : strings) {
    3 if (filter.test(s)) {
    4 System.out.println(s);
    5 }
    6 }
    7}
    8
    9// 使用不同策略
    10processStrings(list, s -> s.startsWith("A"));
    11processStrings(list, s -> s.length() > 5);
  5. 延迟执行/懒加载

    java
    1public String getExpensiveValue(Supplier<String> supplier) {
    2 // 只在需要时调用get()
    3 if (isRequired()) {
    4 return supplier.get(); // 延迟计算
    5 }
    6 return "Default";
    7}

8. 最佳实践

  1. 使用@FunctionalInterface注解标记函数式接口
  2. 优先使用Java标准库提供的函数式接口
  3. 在需要重用的Lambda表达式中,考虑使用方法引用
  4. 利用函数式接口的组合方法构建复杂行为
  5. 为提高性能,针对基本类型使用特化的函数式接口
  6. 不要过度使用Lambda表达式,保持代码可读性
  7. 在文档中清晰说明函数式接口的行为

33. 什么是Java的方法引用?如何使用?

**方法引用(Method Reference)**是Java 8引入的一种语法糖,作为Lambda表达式的简化形式,使代码更加简洁易读。当Lambda表达式的全部内容仅仅是调用一个已存在的方法时,可以使用方法引用来替代。

1. 方法引用的语法

方法引用使用双冒号::操作符,有以下四种形式:

  1. 静态方法引用类名::静态方法名
  2. 特定对象的实例方法引用对象实例::实例方法名
  3. 特定类型的任意对象的实例方法引用类名::实例方法名
  4. 构造方法引用类名::new

2. 方法引用的类型详解

2.1 静态方法引用

引用一个类的静态方法。语法:ClassName::staticMethodName

java
1// Lambda表达式
2Function<String, Integer> parser1 = s -> Integer.parseInt(s);
3
4// 等价的方法引用
5Function<String, Integer> parser2 = Integer::parseInt;
6
7// 使用示例
8int value = parser2.apply("123"); // 返回123

2.2 特定对象的实例方法引用

引用一个特定对象实例的方法。语法:objectReference::instanceMethodName

java
1// Lambda表达式
2Consumer<String> printer1 = s -> System.out.println(s);
3
4// 等价的方法引用
5Consumer<String> printer2 = System.out::println;
6
7// 使用示例
8printer2.accept("Hello World"); // 打印"Hello World"
9
10// 自定义对象的方法引用
11StringBuilder sb = new StringBuilder();
12Consumer<String> appender1 = s -> sb.append(s);
13Consumer<String> appender2 = sb::append;

2.3 特定类型的任意对象的实例方法引用

引用特定类型的任意对象的实例方法。语法:ClassName::instanceMethodName

这种形式适用于Lambda表达式的第一个参数是方法的接收者(调用者),剩余参数是方法的参数。

java
1// Lambda表达式
2Function<String, Integer> lengthFunc1 = s -> s.length();
3
4// 等价的方法引用
5Function<String, Integer> lengthFunc2 = String::length;
6
7// 使用示例
8int len = lengthFunc2.apply("Hello"); // 返回5
9
10// 带参数的方法
11BiPredicate<String, String> contains1 = (s, prefix) -> s.startsWith(prefix);
12BiPredicate<String, String> contains2 = String::startsWith;

2.4 构造方法引用

引用一个类的构造方法。语法:ClassName::new

java
1// 无参构造方法引用
2Supplier<List<String>> listFactory1 = () -> new ArrayList<>();
3Supplier<List<String>> listFactory2 = ArrayList::new;
4
5// 使用示例
6List<String> list = listFactory2.get(); // 创建新的ArrayList实例
7
8// 带参数的构造方法引用
9Function<Integer, List<String>> sizedListFactory = ArrayList::new;
10List<String> sizedList = sizedListFactory.apply(10); // 创建初始容量为10的ArrayList
11
12// 多参数构造方法
13BiFunction<String, Integer, Person> personCreator = Person::new;
14Person person = personCreator.apply("John", 25); // 调用Person(String name, int age)构造方法

3. Lambda表达式与方法引用的对比

3.1 Lambda表达式转换为方法引用的条件

  1. Lambda表达式的全部内容是调用一个已存在的方法
  2. 不对参数做任何修改就传入方法
  3. 不对方法返回值做任何修改

3.2 对比示例

java
1// 示例1:直接调用方法
2// Lambda表达式
3Consumer<String> printer1 = s -> System.out.println(s);
4// 方法引用
5Consumer<String> printer2 = System.out::println;
6
7// 示例2:调用参数的方法
8// Lambda表达式
9Predicate<String> isEmpty1 = s -> s.isEmpty();
10// 方法引用
11Predicate<String> isEmpty2 = String::isEmpty;
12
13// 示例3:使用一个参数调用另一个对象的方法
14// Lambda表达式
15BiPredicate<List<String>, String> contains1 = (list, element) -> list.contains(element);
16// 方法引用
17BiPredicate<List<String>, String> contains2 = List::contains;
18
19// 示例4:构造器引用
20// Lambda表达式
21Function<String, Integer> intConverter1 = s -> new Integer(s);
22// 构造器引用
23Function<String, Integer> intConverter2 = Integer::new;

无法使用方法引用的情况

java
1// 对参数进行处理,无法简化为方法引用
2Function<String, Integer> lengthPlus1 = s -> s.length() + 1;
3
4// 调用方法后对结果进行处理,无法简化为方法引用
5Function<String, Boolean> isEmpty = s -> s.length() == 0;
6
7// 组合多个方法调用,无法简化为方法引用
8Function<String, String> normalize = s -> s.trim().toLowerCase();

4. 方法引用在Stream API中的应用

方法引用与Stream API结合使用非常强大:

java
1// 对象集合处理
2List<Person> persons = Arrays.asList(
3 new Person("John", 25),
4 new Person("Alice", 30),
5 new Person("Bob", 20)
6);
7
8// 获取所有名字
9List<String> names = persons.stream()
10 .map(Person::getName) // 使用方法引用
11 .collect(Collectors.toList());
12
13// 按年龄排序
14persons.sort(Comparator.comparing(Person::getAge));
15
16// 获取年龄总和
17int totalAge = persons.stream()
18 .mapToInt(Person::getAge) // 使用方法引用
19 .sum();
20
21// 分组统计
22Map<Integer, List<Person>> groupByAge = persons.stream()
23 .collect(Collectors.groupingBy(Person::getAge));
24
25// 打印每个人的信息
26persons.forEach(System.out::println); // 使用方法引用

5. 方法引用与集合和数组操作

java
1String[] names = {"John", "Alice", "Bob"};
2List<String> nameList = Arrays.asList(names);
3
4// 数组转List
5List<String> nameList2 = Arrays.stream(names)
6 .collect(Collectors.toList());
7
8// 排序(使用方法引用)
9Arrays.sort(names, String::compareToIgnoreCase);
10nameList.sort(String::compareToIgnoreCase);
11
12// List转数组
13String[] namesArray = nameList.stream()
14 .toArray(String[]::new); // 构造器引用

6. 方法引用的类型推断

Java编译器能够根据上下文自动推断方法引用的签名:

java
1// 自动推断出需要的函数式接口类型
2Runnable r = System.out::println; // 推断为不带参数的println方法
3Consumer<String> c = System.out::println; // 推断为带String参数的println方法
4
5// 自动选择正确的重载方法
6BiFunction<Integer, Integer, Integer> max = Math::max; // 选择int max(int, int)

7. 方法引用与泛型方法

方法引用也可以引用泛型方法:

java
1// 使用泛型方法引用
2List<String> list = Arrays.asList("a", "b", "c");
3List<Integer> lengths = list.stream()
4 .map(String::length) // 泛型方法引用
5 .collect(Collectors.toList());
6
7// 类上的泛型方法引用
8Function<List<String>, String> joiner = String::join; // 引用静态方法String.join(CharSequence, Iterable)

8. 方法引用的优势

  1. 简洁性:代码更加简洁,减少样板代码
  2. 可读性:直接表明调用了哪个方法,意图更清晰
  3. 性能:与等价的Lambda表达式相比没有性能差异
  4. 重用:可以直接利用已有的方法实现

9. 使用建议

  1. 当Lambda表达式仅调用一个现有方法时,优先使用方法引用
  2. 根据实际情况选择最清晰的语法形式
  3. 避免过度使用导致代码难以理解
  4. 在文档中解释复杂的方法引用的用途

34. Java 8中的Optional类是什么?如何正确使用?

Optional 是Java 8引入的一个容器类,代表一个值存在或不存在。Optional的主要目的是解决空指针异常(NullPointerException)问题,提供了一种更优雅的方式来处理可能为null的对象,避免显式的null检查。

1. Optional的核心特点

  • 可以包含非null值,也可以表示"无值"状态
  • 避免null检查的样板代码
  • 使代码更具可读性
  • 迫使开发者思考"值不存在"的情况

2. 创建Optional对象

Optional提供了三种静态工厂方法来创建实例:

java
1// 创建一个空的Optional
2Optional<String> empty = Optional.empty();
3
4// 创建一个包含非null值的Optional
5Optional<String> present = Optional.of("Hello");
6
7// 创建一个可能包含null值的Optional
8Optional<String> nullable = Optional.ofNullable(mayBeNullString);

注意事项

  • Optional.of(null)会抛出NullPointerException
  • Optional.ofNullable(null)会返回一个空的Optional

3. 检查值是否存在

检查Optional是否包含值:

java
1// 使用isPresent方法
2Optional<String> opt = Optional.ofNullable(getValue());
3if (opt.isPresent()) {
4 System.out.println("Value: " + opt.get());
5} else {
6 System.out.println("No value present");
7}
8
9// 使用isEmpty方法 (Java 11+)
10if (opt.isEmpty()) {
11 System.out.println("No value present");
12}

4. 访问Optional中的值

访问Optional中的值有多种方法,根据不同需求选择:

4.1 安全地获取值

java
1// 使用get()方法 (不推荐,可能抛出NoSuchElementException)
2String value = opt.get();
3
4// 使用orElse()方法提供默认值
5String value = opt.orElse("Default value");
6
7// 使用orElseGet()方法提供默认值的供应者
8String value = opt.orElseGet(() -> computeDefaultValue());
9
10// 使用orElseThrow()方法在值不存在时抛出异常
11String value = opt.orElseThrow(); // Java 10+, 抛出NoSuchElementException
12String value = opt.orElseThrow(() -> new IllegalStateException("Value not found"));

4.2 条件操作

java
1// 存在值时执行操作
2opt.ifPresent(value -> System.out.println("Value: " + value));
3
4// 存在值时执行一个操作,否则执行另一个操作 (Java 9+)
5opt.ifPresentOrElse(
6 value -> System.out.println("Value: " + value),
7 () -> System.out.println("No value present")
8);

5. 转换Optional值

Optional提供了转换值的方法,支持链式调用:

java
1// map()方法转换值
2Optional<String> nameOpt = Optional.of("John");
3Optional<Integer> lengthOpt = nameOpt.map(String::length);
4
5// flatMap()方法处理返回Optional的转换
6Optional<User> userOpt = Optional.ofNullable(getUser());
7Optional<String> emailOpt = userOpt.flatMap(User::getEmail);
8// 假设User::getEmail返回Optional<String>
9
10// 过滤值
11Optional<String> longNameOpt = nameOpt.filter(name -> name.length() > 5);
12
13// 或操作 (Java 9+)
14Optional<String> result = opt1.or(() -> opt2);

6. Optional的正确使用模式

6.1 使用map转换值

java
1// 不使用Optional
2User user = getUser();
3String zipCode = null;
4if (user != null) {
5 Address address = user.getAddress();
6 if (address != null) {
7 zipCode = address.getZipCode();
8 }
9}
10
11// 使用Optional
12Optional<User> userOpt = Optional.ofNullable(getUser());
13Optional<String> zipCodeOpt = userOpt
14 .map(User::getAddress)
15 .map(Address::getZipCode);
16String zipCode = zipCodeOpt.orElse("Unknown");

6.2 避免嵌套条件

java
1// 使用Optional链式调用避免嵌套if-else
2String result = Optional.ofNullable(user)
3 .map(User::getAddress)
4 .map(Address::getCity)
5 .map(City::getPostalCode)
6 .orElse("Unknown");

6.3 结合流操作

java
1// 获取有效的邮件地址
2List<String> validEmails = users.stream()
3 .map(User::getEmail)
4 .filter(Optional::isPresent)
5 .map(Optional::get)
6 .collect(Collectors.toList());
7
8// Java 9+中更简洁的写法
9List<String> validEmails = users.stream()
10 .map(User::getEmail)
11 .flatMap(Optional::stream)
12 .collect(Collectors.toList());

7. Optional的常见误用和最佳实践

7.1 不要作为类字段: Optional不是为了作为类的字段而设计的,因为它不可序列化

java
1// 错误:不要这样做
2public class User {
3 private Optional<String> email; // 不推荐
4}
5
6// 正确:直接使用可空字段
7public class User {
8 private String email; // 可以为null
9
10 public Optional<String> getEmail() {
11 return Optional.ofNullable(email);
12 }
13}

7.2 不要作为方法参数: Optional不应该作为方法参数,这会使API更复杂

java
1// 错误:不要这样做
2public void processUser(Optional<User> userOpt) {
3 // ...
4}
5
6// 正确:使用重载或显式参数
7public void processUser(User user) {
8 // 处理非null用户
9}
10
11public void processWithoutUser() {
12 // 处理无用户情况
13}

7.3 避免get()方法: 尽量避免使用get()方法,因为它可能抛出异常

java
1// 不推荐:可能抛出NoSuchElementException
2String name = userOpt.get();
3
4// 推荐:提供默认值或处理异常
5String name = userOpt.orElse("");
6// 或
7String name = userOpt.orElseThrow(() -> new CustomException("User not found"));

7.4 理解orElse和orElseGet的区别

java
1// orElse总是执行默认值表达式,即使Optional有值
2String value = opt.orElse(expensiveOperation());
3
4// orElseGet仅在Optional为空时执行默认值表达式
5String value = opt.orElseGet(() -> expensiveOperation());

8. Java 9及以后的Optional增强

Java 9增加的方法

  • or(): 提供备选Optional
  • ifPresentOrElse(): 存在时执行一个操作,否则执行另一个操作
  • stream(): 将Optional转换为Stream
java
1// or()方法示例
2Optional<String> result = opt1.or(() -> opt2);
3
4// stream()方法示例
5Stream<String> stream = opt.stream(); // 返回0或1个元素的Stream

Java 10增加的方法

  • orElseThrow(): 无参数版本,抛出NoSuchElementException

9. Optional使用的性能考虑

使用Optional可能会引入一些性能开销:

  • 额外的对象创建
  • 包装和解包的成本
  • 方法调用的开销

在性能关键的代码中,可能需要评估使用Optional的成本。对于内部方法,可以考虑显式的null检查,而为公共API提供Optional。

10. 最佳实践总结

  1. 使用场景

    • 作为返回类型,表示可能没有结果
    • 作为包装不确定存在的值
    • 用于链式调用和函数式编程
  2. 避免使用场景

    • 类的字段
    • 方法参数
    • 集合的元素类型
    • 序列化对象
  3. 编码建议

    • 优先使用orElse、orElseGet、orElseThrow而不是get
    • 使用ifPresent或map处理存在的值
    • 链式调用减少嵌套代码
    • 理解并合理使用flatMap
    • 在API边界使用Optional
    • 避免创建嵌套的Optional

35. Java I/O与NIO有什么区别?应该如何选择?

Java I/O和**NIO(New I/O)**是Java中进行输入/输出操作的两个主要API,它们在设计理念、实现方式和适用场景上有显著区别。

1. 基本概念对比

特性Java I/O (io包)Java NIO (nio包)
设计理念面向流 (Stream-oriented)面向缓冲区 (Buffer-oriented)
数据传输方式阻塞式 (Blocking)可选阻塞/非阻塞 (Blocking/Non-blocking)
I/O模型单向传输通道可双向传输
处理器使用线程执行期间阻塞单线程可管理多个连接
缓冲区无内置缓冲区概念基于缓冲区操作
选择器不支持支持Selector多路复用
API复杂性简单直观较复杂
出现时间JDK 1.0JDK 1.4

2. 核心组件比较

2.1 Java I/O的核心组件

  • 字节流:处理字节数据

    • InputStream/OutputStream及其子类
  • 字符流:处理字符数据

    • Reader/Writer及其子类
  • 装饰器模式:通过组合提供额外功能

    • BufferedInputStream/BufferedOutputStream
    • DataInputStream/DataOutputStream
    • 等等

2.2 Java NIO的核心组件

  • Buffer:数据容器

    • ByteBuffer, CharBuffer, IntBuffer
  • Channel:连接到IO设备的通道

    • FileChannel, SocketChannel, ServerSocketChannel
  • Selector:实现多路复用的选择器

    • 允许单线程监控多个Channel的事件
  • Charset:字符集编解码器

    • 处理字节和字符之间的转换

3. 详细功能对比

3.1 数据传输模式

Java I/O

  • 面向流的处理方式
  • 数据被视为连续的字节流
  • 按顺序处理,每次处理一个字节
  • 阻塞式API,调用线程等待操作完成
java
1// I/O读取文件示例
2try (FileInputStream fis = new FileInputStream("file.txt")) {
3 byte[] buffer = new byte[1024];
4 int bytesRead;
5 while ((bytesRead = fis.read(buffer)) != -1) {
6 // 处理读取的数据
7 process(buffer, 0, bytesRead);
8 }
9}

Java NIO

  • 面向缓冲区的处理方式
  • 数据被加载到缓冲区中进行处理
  • 可以前后移动位置(flip, rewind)
  • 支持非阻塞模式
java
1// NIO读取文件示例
2try (FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ)) {
3 ByteBuffer buffer = ByteBuffer.allocate(1024);
4 while (channel.read(buffer) != -1) {
5 buffer.flip(); // 切换到读模式
6 while (buffer.hasRemaining()) {
7 // 处理数据
8 byte b = buffer.get();
9 process(b);
10 }
11 buffer.clear(); // 清空缓冲区准备再次读取
12 }
13}

3.2 阻塞与非阻塞

Java I/O

  • 总是阻塞模式
  • 一个线程只能处理一个连接
  • 高并发场景下需要大量线程

Java NIO

  • 支持非阻塞模式
  • 通过Selector实现多路复用
  • 一个线程可以监控多个Channel
  • 更高效的资源使用
java
1// NIO非阻塞服务器示例
2ServerSocketChannel serverChannel = ServerSocketChannel.open();
3serverChannel.bind(new InetSocketAddress(8080));
4serverChannel.configureBlocking(false);
5
6Selector selector = Selector.open();
7serverChannel.register(selector, SelectionKey.OP_ACCEPT);
8
9while (true) {
10 int readyChannels = selector.select();
11 if (readyChannels == 0) continue;
12
13 Set<SelectionKey> selectedKeys = selector.selectedKeys();
14 Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
15
16 while (keyIterator.hasNext()) {
17 SelectionKey key = keyIterator.next();
18
19 if (key.isAcceptable()) {
20 // 处理连接请求
21 handleAccept(key);
22 } else if (key.isReadable()) {
23 // 处理读事件
24 handleRead(key);
25 }
26
27 keyIterator.remove();
28 }
29}

3.3 内存使用

Java I/O

  • 每次操作都直接在JVM和操作系统间复制数据
  • 不使用中间缓冲区
  • 可能引起频繁的系统调用

Java NIO

  • 使用Buffer作为中间数据存储
  • 减少系统调用次数
  • 可以实现零拷贝(Zero-Copy)
  • 直接缓冲区(Direct Buffer)可以减少JVM和操作系统间的数据复制
java
1// 使用直接缓冲区
2ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
3// 与操作系统内存映射,减少复制

3.4 API复杂度

Java I/O

  • 简单直观的API
  • 易于理解和使用
  • 适合初学者和简单场景

Java NIO

  • 较复杂的API
  • 需要理解Buffer、Channel、Selector等概念
  • 状态管理更复杂(如buffer的position, limit, capacity)
  • 学习曲线较陡

4. 文件操作对比

4.1 读取文件

Java I/O

java
1try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
2 String line;
3 while ((line = reader.readLine()) != null) {
4 System.out.println(line);
5 }
6}

Java NIO

java
1// 小文件读取
2String content = new String(Files.readAllBytes(Paths.get("file.txt")));
3
4// 大文件读取
5try (FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ)) {
6 ByteBuffer buffer = ByteBuffer.allocate(1024);
7 StringBuilder content = new StringBuilder();
8 while (channel.read(buffer) > 0) {
9 buffer.flip();
10 content.append(StandardCharsets.UTF_8.decode(buffer));
11 buffer.clear();
12 }
13 return content.toString();
14}
15
16// 或使用Files工具类(内部使用NIO实现)
17List<String> lines = Files.readAllLines(Paths.get("file.txt"));

4.2 写入文件

Java I/O

java
1try (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"))) {
2 writer.write("Hello, World!");
3 writer.newLine();
4 writer.write("Second line");
5}

Java NIO

java
1// 简单写入
2Files.write(Paths.get("file.txt"), "Hello, World!".getBytes());
3
4// 使用Channel写入
5try (FileChannel channel = FileChannel.open(
6 Paths.get("file.txt"),
7 StandardOpenOption.CREATE,
8 StandardOpenOption.WRITE)) {
9
10 ByteBuffer buffer = ByteBuffer.wrap("Hello, World!".getBytes());
11 channel.write(buffer);
12}

4.3 文件复制

Java I/O

java
1try (InputStream in = new FileInputStream("source.txt");
2 OutputStream out = new FileOutputStream("target.txt")) {
3
4 byte[] buffer = new byte[8192];
5 int bytesRead;
6 while ((bytesRead = in.read(buffer)) != -1) {
7 out.write(buffer, 0, bytesRead);
8 }
9}

Java NIO

java
1// 使用Channel复制
2try (FileChannel sourceChannel = FileChannel.open(Paths.get("source.txt"), StandardOpenOption.READ);
3 FileChannel targetChannel = FileChannel.open(Paths.get("target.txt"),
4 StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
5
6 sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);
7 // 或者: targetChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
8}
9
10// 或使用Files工具类
11Files.copy(Paths.get("source.txt"), Paths.get("target.txt"), StandardCopyOption.REPLACE_EXISTING);

5. 网络编程对比

5.1 阻塞式Socket通信

Java I/O

java
1// 服务器端
2ServerSocket serverSocket = new ServerSocket(8080);
3Socket clientSocket = serverSocket.accept(); // 阻塞等待连接
4BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
5PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
6
7String line;
8while ((line = in.readLine()) != null) { // 阻塞读取
9 out.println("Echo: " + line);
10}
11
12// 客户端
13Socket socket = new Socket("localhost", 8080);
14PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
15BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
16
17out.println("Hello, Server!");
18System.out.println(in.readLine()); // 阻塞等待响应

Java NIO

java
1// 非阻塞服务器
2ServerSocketChannel serverChannel = ServerSocketChannel.open();
3serverChannel.bind(new InetSocketAddress(8080));
4serverChannel.configureBlocking(false);
5
6Selector selector = Selector.open();
7serverChannel.register(selector, SelectionKey.OP_ACCEPT);
8
9while (true) {
10 selector.select(); // 等待事件
11 Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
12
13 while (keys.hasNext()) {
14 SelectionKey key = keys.next();
15 keys.remove();
16
17 if (key.isAcceptable()) {
18 // 处理新连接
19 ServerSocketChannel server = (ServerSocketChannel) key.channel();
20 SocketChannel client = server.accept();
21 client.configureBlocking(false);
22 client.register(selector, SelectionKey.OP_READ);
23 } else if (key.isReadable()) {
24 // 处理读事件
25 SocketChannel client = (SocketChannel) key.channel();
26 ByteBuffer buffer = ByteBuffer.allocate(1024);
27 int bytesRead = client.read(buffer);
28
29 if (bytesRead > 0) {
30 buffer.flip();
31 client.write(buffer);
32 buffer.clear();
33 }
34 }
35 }
36}

6. 性能对比

Java I/O的优势

  • 简单场景下代码更简洁
  • 单个连接处理的逻辑清晰
  • 内存占用较少(不需要为每个连接分配缓冲区)

Java NIO的优势

  • 高并发场景下性能更好
  • 可以用更少的线程处理更多连接
  • 非阻塞模式下CPU利用率更高
  • 大文件传输时零拷贝特性提供更好性能
  • 内存映射文件能提供极高的I/O性能

7. 适用场景分析

7.1 适合Java I/O的场景

  • 连接数量较少且稳定的应用
  • 消息较短的应用
  • 实现简单性比性能更重要的场景
  • 代码可读性要求高的场景
  • 单线程应用

7.2 适合Java NIO的场景

  • 高并发、大量连接的服务器应用
  • 需要非阻塞操作的场景
  • 长连接应用(如即时通讯)
  • 文件操作密集型应用
  • 需要高吞吐量的场景
  • 需要使用异步I/O模型的场景

8. Java 7及以后的改进

Java 7 引入的 NIO.2 (java.nio.file包) 提供了更现代的文件系统API:

  • Path 接口替代File类
  • Files 工具类提供丰富的文件操作方法
  • 文件属性操作更加强大
  • WatchService 用于监视文件系统变化
  • 异步I/O 支持(AsynchronousFileChannel等)
java
1// NIO.2 文件操作示例
2Path path = Paths.get("file.txt");
3Files.write(path, "Hello World".getBytes(), StandardOpenOption.CREATE);
4
5// 异步文件操作
6AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
7 path, StandardOpenOption.READ);
8ByteBuffer buffer = ByteBuffer.allocate(1024);
9
10fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
11 @Override
12 public void completed(Integer result, ByteBuffer attachment) {
13 attachment.flip();
14 CharBuffer charBuffer = StandardCharsets.UTF_8.decode(attachment);
15 System.out.println(charBuffer.toString());
16 }
17
18 @Override
19 public void failed(Throwable exc, ByteBuffer attachment) {
20 exc.printStackTrace();
21 }
22});

9. 选择指南

选择Java I/O还是NIO时,考虑以下因素:

  • 并发连接数:高并发场景选择NIO
  • 业务复杂度:简单业务逻辑可选I/O
  • 性能要求:对性能要求高选择NIO
  • 开发团队经验:团队对NIO不熟悉时,权衡开发成本
  • 代码可维护性:I/O代码通常更易维护
  • 阻塞需求:需要非阻塞操作必须选NIO
  • 内存资源限制:线程资源紧张选择NIO

10. 最佳实践

  1. 不要混用:一个系统中尽量不要混用I/O和NIO
  2. 考虑使用框架:如Netty, MINA等封装了NIO的复杂性
  3. NIO调试复杂:做好日志记录和异常处理
  4. 文件操作推荐NIO.2:现代Java应用应使用java.nio.file包
  5. 网络应用中的选择
    • 小规模应用可以使用I/O
    • 高并发应用选择NIO或基于NIO的框架
  6. 注意缓冲区管理:在NIO中正确管理Buffer的状态

36. Java中的垃圾回收机制是如何工作的?

**垃圾回收(Garbage Collection, GC)**是Java虚拟机(JVM)自动管理内存的核心机制,它能够自动识别和清理不再使用的对象,释放内存资源。理解垃圾回收机制对于编写高效的Java应用至关重要。

1. 垃圾回收的基本原理

垃圾回收的基本思路是识别哪些对象是"存活"的,然后将其他对象视为"垃圾"进行回收。JVM采用的判断对象是否可以回收的算法主要有两种:

  • 引用计数法(Reference Counting)

    • 为每个对象维护一个引用计数器
    • 每当有引用指向对象时计数器加1,引用失效时减1
    • 计数为0时对象可被回收
    • 缺点:无法处理循环引用问题
  • 可达性分析(Reachability Analysis)

    • 从一系列"GC Roots"开始,进行引用链搜索
    • 可从GC Roots直接或间接到达的对象被视为存活
    • 不可达对象被视为垃圾
    • JVM采用这种算法

2. GC Roots包括

  • 虚拟机栈中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象
  • JVM内部引用(类加载器、异常对象等)

3. Java中的引用类型

JDK 1.2后,Java将引用分为四种类型,为GC提供了更灵活的内存管理策略:

  • 强引用(Strong Reference)

    • 最常见的引用(如Object obj = new Object()
    • 只要强引用存在,对象就不会被回收
  • 软引用(Soft Reference)

    • 内存不足时才会被回收
    • 用于实现内存敏感的缓存
    • 通过SoftReference类实现
  • 弱引用(Weak Reference)

    • 下一次GC时无条件回收
    • 用于实现规范化映射
    • 通过WeakReferenceWeakHashMap实现
  • 虚引用(Phantom Reference)

    • 不影响对象生命周期,仅用于对象被回收时收到通知
    • 必须和引用队列一起使用
    • 通过PhantomReference类实现

4. 垃圾回收算法

4.1 标记-清除算法(Mark-Sweep)

  • 过程:首先标记所有需要回收的对象,然后统一回收所有被标记的对象
  • 优点:算法简单
  • 缺点
    • 效率不高(标记和清除两个过程效率都不高)
    • 会产生大量内存碎片
1[标记前] O O X O X O O X O X
2[标记后] O O M O M O O M O M (M=被标记为垃圾)
3[清除后] O O _ O _ O O _ O _ (_=空闲内存)

4.2 复制算法(Copying)

  • 过程:将内存分为两块,每次只使用一块;当一块用完时,将存活对象复制到另一块,然后清空当前块
  • 优点
    • 解决内存碎片问题
    • 实现简单,运行高效
  • 缺点
    • 内存利用率降低
    • 对象存活率高时效率会降低
1[复制前] [O O X O X O] [ ] (X=垃圾对象)
2[复制后] [ ] [O O O O ] (存活对象被复制)

4.3 标记-整理算法(Mark-Compact)

  • 过程:标记阶段与标记-清除算法一样,但后续不是直接清理,而是将存活对象移动到内存的一端,然后清理边界以外的内存
  • 优点
    • 解决了内存碎片问题
    • 避免了复制算法的空间浪费
  • 缺点
    • 需要移动对象,效率低于复制算法
1[标记后] O O M O M O O M O M (M=被标记为垃圾)
2[整理后] O O O O O O _ _ _ _ (_=空闲内存)

4.4 分代收集算法(Generational Collection)

  • 核心思想:根据对象存活周期将内存划分为几个区域
  • 实现:将堆分为新生代和老年代,不同代使用不同的收集算法
  • 优点
    • 针对不同年龄对象采用最合适的收集算法
    • 提高GC效率

5. 堆内存结构和分代回收

在经典的分代垃圾回收模型中,堆内存被划分为:

5.1 新生代(Young Generation)

  • Eden区:大多数新创建的对象最初都在Eden区分配
  • Survivor区:包括From和To两个区域,存放经过GC后幸存的对象

5.2 老年代(Old Generation)

  • 存放长期存活的对象
  • 通常经历了多次GC仍存活的对象会被移到这里

5.3 对象的生命周期

  1. 新创建的对象首先分配在Eden区
  2. Eden区满后触发Minor GC,存活对象移动到Survivor区
  3. 经过多次Minor GC仍存活的对象进入老年代
  4. 老年代满时触发Major GC或Full GC

6. 垃圾回收器的类型

JVM提供了多种垃圾回收器,适用于不同的应用场景:

6.1 Serial收集器

  • 单线程收集器,GC时暂停所有应用线程
  • 简单高效,适合单核CPU和小内存环境

6.2 ParNew收集器

  • Serial的多线程版本
  • 常与CMS收集器配合使用

6.3 Parallel Scavenge收集器

  • 关注吞吐量的多线程收集器
  • 可控制吞吐量和最大暂停时间

6.4 Serial Old收集器

  • Serial收集器的老年代版本
  • 单线程,使用标记-整理算法

6.5 Parallel Old收集器

  • Parallel Scavenge的老年代版本
  • 多线程,使用标记-整理算法

6.6 CMS(Concurrent Mark Sweep)收集器

  • 以最短停顿时间为目标
  • 基于标记-清除算法
  • 过程:初始标记→并发标记→重新标记→并发清除
  • 优点:并发收集,停顿时间短
  • 缺点:会产生内存碎片,对CPU资源敏感

6.7 G1(Garbage-First)收集器

  • JDK 7更新,JDK 9默认
  • 将堆划分为多个区域(Region)
  • 优先收集垃圾最多的区域
  • 过程:初始标记→并发标记→最终标记→筛选回收
  • 优点:可预测停顿时间,无内存碎片

6.8 ZGC(Z Garbage Collector)

  • JDK 11引入的低延迟收集器
  • 可处理TB级内存,停顿时间10ms
  • 基于Region,使用读屏障和有色指针

6.9 Shenandoah收集器

  • OpenJDK特有,与ZGC目标类似
  • 通过并发标记和复制实现低延迟

7. GC触发条件

7.1 Minor GC触发条件

  • Eden区空间不足时

7.2 Major/Full GC触发条件

  • 老年代空间不足
  • Permanent Generation(永久代)/Metaspace空间不足
  • System.gc()调用(建议JVM执行GC,但不保证执行)
  • CMS GC出现promotion failed和concurrent mode failure
  • Minor GC晋升到老年代的对象大小超过老年代剩余空间

8. GC调优参数

8.1 堆大小相关

1-Xms: 初始堆大小
2-Xmx: 最大堆大小
3-Xmn: 年轻代大小
4-XX:SurvivorRatio: Eden区与Survivor区比例
5-XX:NewRatio: 年轻代与老年代比例

8.2 GC策略相关

1-XX:+UseSerialGC: 使用Serial+Serial Old收集器
2-XX:+UseParallelGC: 使用Parallel Scavenge+Parallel Old收集器
3-XX:+UseConcMarkSweepGC: 使用ParNew+CMS+Serial Old收集器
4-XX:+UseG1GC: 使用G1收集器
5-XX:+UseZGC: 使用ZGC收集器

8.3 GC日志相关

1-XX:+PrintGCDetails: 打印GC详细信息
2-XX:+PrintGCTimeStamps: 打印GC时间戳
3-Xloggc:filename: 输出GC日志到文件

9. 对象分配与回收优化

9.1 TLAB(Thread Local Allocation Buffer)

  • 线程私有的对象分配区域
  • 减少多线程分配对象时的同步开销
  • 提高对象分配效率

9.2 逃逸分析

  • JVM的一种优化技术
  • 分析对象的作用域,确定对象是否可以在栈上分配
  • 如果对象不会"逃逸"出方法,可以在栈上分配

9.3 写屏障和读屏障

  • 在分代回收中用于记录跨代引用
  • 确保垃圾回收的正确性
  • 影响GC性能

10. JVM内存泄漏常见原因

  1. 静态集合类:静态集合持有对象引用
  2. 未关闭的资源:如文件、数据库连接等
  3. 监听器和回调:注册但未注销
  4. 单例对象:持有外部对象引用
  5. 内部类和匿名内部类:隐式持有外部类引用
java
1// 内存泄漏示例
2public class LeakExample {
3 // 静态集合导致的内存泄漏
4 private static final List<Object> LEAK_LIST = new ArrayList<>();
5
6 public void addToList(Object obj) {
7 LEAK_LIST.add(obj); // 对象将永远不会被回收
8 }
9
10 // 未关闭资源导致的内存泄漏
11 public void processFile(String path) throws Exception {
12 InputStream is = new FileInputStream(path);
13 // 使用流但未关闭,可能导致文件句柄泄漏
14 // is.close(); 应该在finally块中关闭
15 }
16}

11. 实际应用中的GC调优

11.1 调优目标

  • 减少Full GC频率
  • 降低GC停顿时间
  • 提高吞吐量

11.2 调优步骤

  1. 确定目标:是降低延迟还是提高吞吐量
  2. 收集GC数据:使用JVM参数开启GC日志
  3. 分析GC日志:使用工具如GCViewer分析
  4. 调整参数:根据分析结果调整JVM参数
  5. 验证效果:重复收集数据并比较结果

11.3 常见调优实践

  • 选择合适的收集器
  • 调整堆大小和代的比例
  • 设置合理的初始堆大小,避免频繁扩容
  • 对大内存应用考虑G1或ZGC
  • 避免创建大量临时对象
  • 复用对象而非频繁创建

37. Java中的类加载机制是怎样的?

**类加载机制(Class Loading Mechanism)**是Java虚拟机将类的字节码加载到内存、创建对象、链接和初始化的过程,是Java动态扩展能力的重要体现。

1. 类加载的时机

类加载通常在以下情况触发:

  1. 创建类实例:使用new关键字、反射、克隆、反序列化
  2. 访问类的静态变量:首次引用类的静态字段(final常量除外)
  3. 访问类的静态方法
  4. 初始化子类时:会先初始化父类
  5. Java虚拟机启动时的主类
  6. 使用反射API:如Class.forName()
  7. 使用JDK 7动态语言支持:MethodHandle和MethodType

注意:通过数组定义引用类、访问类的final静态字段、子类引用父类的静态字段不会触发子类初始化。

2. 类加载的过程

类加载过程分为五个阶段:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)。其中验证、准备、解析统称为连接(Linking)。

2.1 加载(Loading)

加载是类加载的第一步,主要完成三件事:

  1. 通过类的全限定名获取类的二进制字节流
  2. 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的访问入口

获取字节流的方式多种多样:

  • 从ZIP包中获取(如JAR、WAR、EAR文件)
  • 从网络中获取(如Applet)
  • 运行时计算生成(如动态代理)
  • 从数据库中获取
  • 从其他文件中获取(如JSP)

2.2 验证(Verification)

验证是连接阶段的第一步,目的是确保类的字节码符合JVM规范,不会危害虚拟机安全。主要包括:

  • 文件格式验证:魔数、版本号、常量池等
  • 元数据验证:是否符合Java语言规范
  • 字节码验证:确保程序语义正确性
  • 符号引用验证:确保解析能正常进行

2.3 准备(Preparation)

准备阶段为类的静态变量分配内存并设置初始值(零值),如:

java
1public static int value = 123; // 准备阶段value值为0,而非123
2public static final int CONSTANT = 123; // 准备阶段CONSTANT值即为123

这个阶段不包含实例变量,实例变量会在对象实例化时随对象分配到堆中。

2.4 解析(Resolution)

解析阶段是将常量池内的符号引用替换为直接引用的过程。符号引用包括:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

解析可能在初始化之后才开始,这是为了支持Java的动态绑定。

2.5 初始化(Initialization)

初始化是类加载的最后一步,会执行类构造器<clinit>()方法。这个方法由编译器自动生成,包含:

  • 静态变量的赋值操作
  • 静态代码块中的语句

执行顺序与源文件中出现的顺序一致:

java
1public class InitExample {
2 static {
3 i = 0; // 可以赋值,但不能访问
4 // System.out.println(i); // 非法前向引用
5 }
6
7 static int i = 1; // 最终i的值为1
8
9 static {
10 i = 2; // 修改为2
11 System.out.println(i); // 输出2
12 }
13}

初始化的特点

  • JVM保证类的<clinit>()方法在多线程环境中被正确加锁、同步
  • 父类的初始化先于子类
  • 接口初始化不会导致父接口初始化
  • 接口中的变量默认是static final的

3. 类加载器

类加载器(ClassLoader)负责加载阶段,通过类的全限定名查找并加载类的二进制数据。

3.1 Java的类加载器分类

3.1.1 启动类加载器(Bootstrap ClassLoader)

  • 负责加载Java核心类库
  • 位于JRE的lib目录,如rt.jar
  • 由C++实现,是JVM的一部分
  • 在Java代码中无法直接引用

3.1.2 扩展类加载器(Extension ClassLoader)

  • 负责加载JRE的扩展目录(lib/ext
  • JDK 9后更名为平台类加载器(Platform ClassLoader)

3.1.3 应用程序类加载器(Application ClassLoader)

  • 负责加载用户类路径(ClassPath)上的类
  • 也称系统类加载器(System ClassLoader)
  • 是应用程序默认的类加载器

3.1.4 自定义类加载器

  • 通过继承java.lang.ClassLoader实现
  • 常用于实现特殊的类加载逻辑

3.2 双亲委派模型

类加载器使用双亲委派模型(Parents Delegation Model)进行组织。当一个类加载器收到类加载请求时,首先将请求委派给父加载器,依次向上。只有当父加载器无法加载时,子加载器才尝试自己加载。

![类加载器层次结构]

1BootstrapClassLoader
2
3ExtensionClassLoader
4
5ApplicationClassLoader
6
7 自定义ClassLoader

双亲委派的工作流程

  1. 检查类是否已被加载
  2. 如果未加载,委托给父加载器
  3. 如果父加载器无法加载,自己尝试加载

ClassLoader的loadClass方法源码简化版

java
1protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
2 // 检查类是否已加载
3 Class<?> c = findLoadedClass(name);
4 if (c == null) {
5 try {
6 if (parent != null) {
7 // 委托父加载器加载
8 c = parent.loadClass(name, false);
9 } else {
10 // 使用启动类加载器加载
11 c = findBootstrapClassOrNull(name);
12 }
13 } catch (ClassNotFoundException e) {
14 // 父加载器无法加载
15 }
16
17 if (c == null) {
18 // 父加载器无法加载,自己尝试加载
19 c = findClass(name);
20 }
21 }
22 if (resolve) {
23 resolveClass(c);
24 }
25 return c;
26}

双亲委派的好处

  • 避免类的重复加载
  • 保护核心API不被篡改(安全性)
  • 确保类加载的层次性和优先级

3.3 打破双亲委派模型

有些场景需要打破双亲委派模型:

  1. JDK 1.2之前:自定义ClassLoader无法通过双亲委派
  2. SPI机制:如JDBC、JCE、JNDI等
  3. OSGi模块化:允许同一个类在不同的模块中加载
  4. Tomcat类加载机制:实现Web应用隔离
  5. 热部署和热替换:如Spring开发工具、JRebel

打破双亲委派的实现方式

  • 重写loadClass()方法
  • 使用上下文类加载器(Thread Context ClassLoader)

上下文类加载器示例

java
1// 设置上下文类加载器
2Thread.currentThread().setContextClassLoader(customClassLoader);
3
4// 获取上下文类加载器
5ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
6
7// 使用上下文类加载器加载类
8Class<?> clazz = Class.forName("com.example.MyClass", true, contextClassLoader);

3.4 自定义类加载器

实现自定义类加载器通常只需要重写findClass()方法:

java
1public class CustomClassLoader extends ClassLoader {
2 @Override
3 protected Class<?> findClass(String name) throws ClassNotFoundException {
4 // 获取类的字节码
5 byte[] classData = loadClassData(name);
6 if (classData == null) {
7 throw new ClassNotFoundException(name);
8 }
9
10 // 调用defineClass将字节码转为Class对象
11 return defineClass(name, classData, 0, classData.length);
12 }
13
14 private byte[] loadClassData(String name) {
15 // 实现自定义的字节码获取逻辑
16 // 如从特定位置读取类文件、网络下载、动态生成等
17 // 返回类的字节码数组
18 }
19}

使用自定义类加载器

java
1CustomClassLoader loader = new CustomClassLoader();
2Class<?> clazz = loader.loadClass("com.example.MyClass");
3Object instance = clazz.newInstance();

自定义类加载器的应用场景

  • 加密解密类字节码(安全)
  • 从网络或数据库加载类
  • 热部署
  • 类隔离(不同版本的类共存)
  • 实现模块化系统(如OSGi)

4. Class对象和类加载器

Java中,类由其全限定名和加载它的类加载器共同确定其唯一性。不同类加载器加载的同名类被视为不同的类,它们的Class对象不相等。

java
1ClassLoader loader1 = new CustomClassLoader();
2ClassLoader loader2 = new CustomClassLoader();
3
4Class<?> class1 = loader1.loadClass("com.example.MyClass");
5Class<?> class2 = loader2.loadClass("com.example.MyClass");
6
7// 尽管类名相同,但由不同的类加载器加载
8System.out.println(class1 == class2); // 输出false

5. 类加载器和命名空间

每个类加载器都有自己的命名空间,由该加载器及其所有父加载器加载的类组成。

命名空间特点

  • 子加载器能访问父加载器加载的类
  • 父加载器不能访问子加载器加载的类
  • 同级但不同的类加载器相互隔离

这种隔离机制是实现应用隔离(如Web容器中的应用隔离)的基础。

6. 类加载的并行性

JVM规范没有强制要求类加载过程的并行性。但在实际实现中:

  • 加载阶段可以并行
  • 验证阶段可以并行
  • 初始化阶段必须是线程安全的

7. 动态加载与反射

Java支持运行时动态加载类:

java
1// 动态加载类
2Class<?> clazz = Class.forName("com.example.MyClass");
3
4// 创建实例
5Object obj = clazz.newInstance();
6
7// 获取方法
8Method method = clazz.getMethod("methodName", paramTypes);
9
10// 调用方法
11method.invoke(obj, params);

与直接使用new关键字相比,动态加载提供了更大的灵活性,是Java反射API和很多框架的基础。

8. JDK 9模块系统的影响

JDK 9引入了模块系统(JPMS),对类加载机制产生了影响:

  • 新增了模块路径(Module Path),与传统类路径并存
  • 增强了类加载的安全性和访问控制
  • 引入平台类加载器替代扩展类加载器
  • 模块之间的依赖关系显式声明

9. 常见问题与解决方案

9.1 ClassNotFoundException vs NoClassDefFoundError

  • ClassNotFoundException: 当应用尝试通过Class.forName、ClassLoader.loadClass或ClassLoader.findSystemClass加载类但找不到时抛出
  • NoClassDefFoundError: 当Java虚拟机或ClassLoader实例尝试加载类的定义但找不到时抛出,通常是类加载时的链接错误

9.2 类加载器泄漏

  • 发生在类加载器无法被垃圾回收的情况
  • 常见于Web容器中的应用重新部署
  • 解决方案:正确管理类加载器生命周期,使用弱引用

38. Java中的异常处理机制是怎样的?

异常处理机制是Java提供的处理程序运行时错误的强大工具,它能够改变程序的正常控制流程以处理错误情况,同时保持代码的结构性和可读性。

1. Java异常体系结构

Java异常体系以Throwable类为根,包含两个主要分支:ErrorException

1.1 Throwable类

  • 所有异常的父类
  • 包含错误信息和栈追踪
  • 主要方法:getMessage(), printStackTrace(), getStackTrace()

1.2 Error

  • 表示严重的系统级错误,通常是不可恢复的
  • 程序通常无法处理
  • 例如:OutOfMemoryError, StackOverflowError, VirtualMachineError

1.3 Exception

  • 表示程序可能处理的错误情况
  • 分为两类:已检查异常(Checked Exception)和未检查异常(Unchecked Exception)

异常继承体系图

1Throwable
2 / \
3 Error Exception
4 / \
5 (Checked Exceptions) RuntimeException
6 |
7 (Unchecked Exceptions)

2. 已检查异常(Checked Exception)与未检查异常(Unchecked Exception)

2.1 已检查异常(Checked Exception)

  • 编译时期就会检查的异常
  • 必须显式处理(try-catch或throws声明)
  • Exception的直接子类(除RuntimeException)
  • 表示可预见但可能避免不了的异常情况
  • 常见例子:IOException, SQLException, ClassNotFoundException

2.2 未检查异常(Unchecked Exception)

  • 编译时期不会检查的异常
  • 不需要显式处理
  • RuntimeException及其子类
  • 通常表示程序错误
  • 常见例子:NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException

3. 异常处理语法

Java提供了try, catch, finally, throw和throws关键字来处理异常。

3.1 try-catch语句

java
1try {
2 // 可能抛出异常的代码
3 int result = 10 / 0; // 会抛出ArithmeticException
4} catch (ArithmeticException e) {
5 // 处理特定类型的异常
6 System.out.println("除零错误: " + e.getMessage());
7} catch (Exception e) {
8 // 处理其他类型的异常
9 System.out.println("发生错误: " + e.getMessage());
10}

3.2 多catch块和异常类型合并

Java 7引入了多种异常类型的合并处理:

java
1try {
2 // 可能抛出多种异常的代码
3} catch (IOException | SQLException e) {
4 // 处理IOException或SQLException
5 System.out.println("IO或SQL异常: " + e.getMessage());
6}

3.3 finally语句

无论是否发生异常,finally块中的代码总会执行(除非JVM退出):

java
1FileInputStream fis = null;
2try {
3 fis = new FileInputStream("file.txt");
4 // 处理文件
5} catch (IOException e) {
6 System.out.println("文件处理错误: " + e.getMessage());
7} finally {
8 // 清理资源,无论是否发生异常
9 if (fis != null) {
10 try {
11 fis.close();
12 } catch (IOException e) {
13 // 处理关闭时可能发生的异常
14 }
15 }
16}

3.4 try-with-resources语句

Java 7引入的自动资源管理语法,适用于实现AutoCloseable接口的对象:

java
1try (FileInputStream fis = new FileInputStream("file.txt");
2 FileOutputStream fos = new FileOutputStream("output.txt")) {
3 // 处理文件
4 byte[] buffer = new byte[1024];
5 int length;
6 while ((length = fis.read(buffer)) > 0) {
7 fos.write(buffer, 0, length);
8 }
9} catch (IOException e) {
10 System.out.println("文件处理错误: " + e.getMessage());
11}
12// 资源会自动关闭,无需finally块

3.5 throw语句

用于显式抛出异常:

java
1public void deposit(double amount) {
2 if (amount <= 0) {
3 throw new IllegalArgumentException("存款金额必须为正数");
4 }
5 // 处理存款逻辑
6}

3.6 throws声明

在方法签名中声明可能抛出的已检查异常:

java
1public void readFile(String fileName) throws IOException {
2 FileInputStream fis = new FileInputStream(fileName);
3 // 处理文件
4}

3.7 异常链

传递原始异常信息:

java
1try {
2 // 可能抛出异常的代码
3} catch (SQLException e) {
4 // 包装原始异常,并提供更多上下文
5 throw new ServiceException("数据库操作失败", e);
6}

4. 自定义异常

创建自定义异常通常需要继承Exception(已检查异常)或RuntimeException(未检查异常):

java
1// 已检查异常
2public class InsufficientFundsException extends Exception {
3 private double amount;
4
5 public InsufficientFundsException(double amount) {
6 super("余额不足,还差" + amount + "元");
7 this.amount = amount;
8 }
9
10 public double getAmount() {
11 return amount;
12 }
13}
14
15// 未检查异常
16public class UserNotFoundException extends RuntimeException {
17 private String userId;
18
19 public UserNotFoundException(String userId) {
20 super("找不到用户: " + userId);
21 this.userId = userId;
22 }
23
24 public String getUserId() {
25 return userId;
26 }
27}

使用自定义异常

java
1public void withdraw(double amount) throws InsufficientFundsException {
2 if (amount > balance) {
3 throw new InsufficientFundsException(amount - balance);
4 }
5 balance -= amount;
6}
7
8public User findUser(String userId) {
9 User user = userRepository.findById(userId);
10 if (user == null) {
11 throw new UserNotFoundException(userId);
12 }
13 return user;
14}

5. 异常处理最佳实践

5.1 异常粒度

  • 粒度适中,既不过细也不过粗
  • 精确捕获需要处理的异常
  • 不捕获无法处理的异常

5.2 异常转换

  • 底层异常转换为应用级异常
  • 保留原始异常信息(使用异常链)
  • 提供有意义的错误消息
java
1try {
2 // 数据库操作
3} catch (SQLException e) {
4 throw new ServiceException("数据库查询失败", e);
5}

5.3 异常处理与日志

  • 不要捕获异常后不处理或仅打印
  • 记录异常的完整信息,包括栈跟踪
  • 避免过度记录(如多层传递的异常)
java
1try {
2 // 操作
3} catch (Exception e) {
4 logger.error("操作失败", e); // 记录完整异常
5 throw e; // 如果需要,可以重新抛出
6}

5.4 资源管理

  • 优先使用try-with-resources
  • 确保所有资源都能正确关闭
  • 资源关闭顺序与打开顺序相反

5.5 异常设计原则

  • 已检查异常用于可恢复的情况
  • 未检查异常用于编程错误
  • 避免过多的已检查异常
  • 不要使用异常控制正常流程

5.6 性能考虑

  • 异常处理有性能开销
  • 避免用异常处理正常情况
  • 避免过度细粒度的try-catch块

错误示例:

java
1// 不要这样做
2for (int i = 0; i < list.size(); i++) {
3 try {
4 process(list.get(i));
5 } catch (Exception e) {
6 logger.error("处理元素失败", e);
7 }
8}

正确示例:

java
1// 更好的方式
2try {
3 for (int i = 0; i < list.size(); i++) {
4 process(list.get(i));
5 }
6} catch (Exception e) {
7 logger.error("处理列表失败", e);
8}

6. Java 7以后的异常处理增强

6.1 try-with-resources

  • 自动关闭实现AutoCloseable的资源
  • 优于传统finally块
  • 可以处理多个资源
  • 可以捕获关闭资源时的异常

6.2 多异常捕获

  • 使用|合并多个异常处理
  • 减少代码重复
  • 被捕获的异常类型必须没有继承关系

6.3 更精确的重抛异常

  • Java 7以前,重抛异常会丢失具体异常类型
  • Java 7以后,编译器会分析可能抛出的异常类型
java
1// Java 7之前
2public void method() throws Exception { // 只能声明为Exception
3 try {
4 // 可能抛出IOException或SQLException
5 } catch (Exception e) {
6 // 记录日志
7 throw e; // 编译器认为这里抛出的是Exception
8 }
9}
10
11// Java 7之后
12public void method() throws IOException, SQLException { // 可以更精确地声明
13 try {
14 // 可能抛出IOException或SQLException
15 } catch (Exception e) {
16 // 记录日志
17 throw e; // 编译器可以分析出实际可能的异常类型
18 }
19}

7. 常见异常及其处理方式

7.1 NullPointerException

  • 尝试访问null对象的方法或属性
  • 防御性编程:检查null值
  • 考虑使用Optional(Java 8+)
java
1// 预防NPE
2if (obj != null) {
3 obj.method();
4}
5
6// 使用Optional
7Optional<User> userOpt = userService.findUser(id);
8userOpt.ifPresent(user -> user.updateProfile());

7.2 ArrayIndexOutOfBoundsException

  • 访问数组越界
  • 检查索引范围
  • 使用集合类替代数组
java
1// 防止越界
2if (index >= 0 && index < array.length) {
3 value = array[index];
4}

7.3 ClassCastException

  • 错误的类型转换
  • 使用instanceof检查
  • 使用泛型避免类型转换
java
1// 安全的类型转换
2if (obj instanceof String) {
3 String str = (String) obj;
4 // 使用str
5}

7.4 IOException

  • I/O操作失败
  • 使用try-with-resources
  • 考虑重试机制

7.5 SQLException

  • 数据库操作失败
  • 使用事务管理
  • 处理特定的SQL错误码

7.6 OutOfMemoryError

  • 内存不足
  • 增加JVM内存
  • 检查内存泄漏
  • 优化大对象处理

8. 异常处理策略

8.1 集中式异常处理

在应用程序的顶层捕获并统一处理异常:

java
1// Spring MVC中的全局异常处理
2@ControllerAdvice
3public class GlobalExceptionHandler {
4
5 @ExceptionHandler(UserNotFoundException.class)
6 public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
7 ErrorResponse error = new ErrorResponse("USER_NOT_FOUND", e.getMessage());
8 return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
9 }
10
11 @ExceptionHandler(Exception.class)
12 public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
13 ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
14 return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
15 }
16}

8.2 事务性资源处理

在异常发生时回滚事务:

java
1@Transactional
2public void transferMoney(Account from, Account to, BigDecimal amount) {
3 from.withdraw(amount); // 可能抛出InsufficientFundsException
4 to.deposit(amount);
5 // 如果发生异常,事务会自动回滚
6}

8.3 重试机制

对于可能暂时失败的操作实现重试逻辑:

java
1public Response callExternalService() {
2 int maxRetries = 3;
3 int attempt = 0;
4
5 while (attempt < maxRetries) {
6 try {
7 return externalService.call(); // 可能抛出NetworkException
8 } catch (NetworkException e) {
9 attempt++;
10 if (attempt == maxRetries) {
11 throw new ServiceUnavailableException("服务不可用", e);
12 }
13 // 指数退避
14 try {
15 Thread.sleep(100 * (long)Math.pow(2, attempt));
16 } catch (InterruptedException ie) {
17 Thread.currentThread().interrupt();
18 throw new ServiceUnavailableException("重试中断", ie);
19 }
20 }
21 }
22 // 不应该执行到这里
23 throw new IllegalStateException("重试逻辑错误");
24}

9. Java异常处理的成本

异常处理会带来性能成本,主要包括:

  1. 创建异常对象:构建异常对象和获取栈跟踪的成本
  2. 栈展开:JVM需要回溯调用栈
  3. 异常处理搜索:JVM查找匹配的catch块

性能优化建议:

  • 不要用异常控制正常流程
  • 避免捕获再立即重抛
  • 精确捕获可能的异常
  • 适当使用日志级别

39. Java集合框架是如何设计的?常用集合类的底层实现是怎样的?

**Java集合框架(Java Collections Framework)**是Java标准库中用于存储和操作数据集合的架构。它提供了一系列接口和类,使开发者能够以统一、高效的方式处理数据集合。

1. 集合框架的整体设计

Java集合框架的设计遵循了几个核心原则:

  • 统一的架构:所有集合类都实现共同的接口
  • 接口与实现分离:通过接口定义行为,具体类实现细节
  • 互操作性:集合之间可以相互转换
  • 性能高效:集合实现针对不同场景进行优化
  • 扩展性:允许用户自定义集合类

1.1 集合框架的基本结构

Java集合框架主要包含以下几个核心接口:

![集合框架的继承关系]

1Iterable
2 |
3 Collection
4 / | \
5 / | \
6 Set List Queue
7 / \ \
8 / \ \
9 SortedSet RandomAccess Deque
10 |
11 NavigableSet

1.2 Map接口及其实现

Map接口虽然不是Collection的子接口,但也是集合框架的重要组成部分:

1Map
2 /|\
3 / | \
4 / | \
5SortedMap | ConcurrentMap
6 | |
7NavigableMap

2. 主要接口及其特点

2.1 Collection接口

  • 集合层次结构的根接口
  • 定义了所有集合共有的基本操作
  • 主要方法:add(), remove(), contains(), size(), iterator()

2.2 List接口

  • 有序集合,元素可以重复
  • 支持按索引访问元素
  • 主要实现:ArrayList, LinkedList, Vector

2.3 Set接口

  • 不允许重复元素的集合
  • 不保证元素顺序(特定实现除外)
  • 主要实现:HashSet, LinkedHashSet, TreeSet

2.4 Queue接口

  • 通常用于存储等待处理的元素
  • 按特定顺序(如FIFO)检索元素
  • 主要实现:LinkedList, PriorityQueue

2.5 Map接口

  • 键值对映射
  • 键不能重复
  • 主要实现:HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap

3. List实现类的底层原理

3.1 ArrayList

  • 数据结构:基于动态数组
  • 随机访问性能:O(1),通过索引访问元素
  • 内部实现
    • 默认初始容量为10
    • 动态增长:当容量不足时,扩容为当前容量的1.5倍
    • 使用transient修饰符避免序列化整个数组
    • 支持快速的随机访问
    • 非线程安全
java
1// ArrayList核心源码简化版
2public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
3 // 实际存储元素的数组
4 transient Object[] elementData;
5 // 当前元素数量
6 private int size;
7
8 // 添加元素
9 public boolean add(E e) {
10 ensureCapacityInternal(size + 1); // 确保容量足够
11 elementData[size++] = e;
12 return true;
13 }
14
15 // 按索引获取元素
16 public E get(int index) {
17 rangeCheck(index);
18 return (E) elementData[index];
19 }
20
21 // 确保内部容量
22 private void ensureCapacityInternal(int minCapacity) {
23 if (minCapacity - elementData.length > 0)
24 grow(minCapacity);
25 }
26
27 // 扩容逻辑
28 private void grow(int minCapacity) {
29 int oldCapacity = elementData.length;
30 int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
31 if (newCapacity < minCapacity) newCapacity = minCapacity;
32 elementData = Arrays.copyOf(elementData, newCapacity);
33 }
34}

3.2 LinkedList

  • 数据结构:双向链表
  • 随机访问性能:O(n),需要从头/尾遍历
  • 内部实现
    • 每个元素都是一个包含前驱和后继引用的节点
    • 同时实现了List和Deque接口
    • 适合频繁的插入和删除操作
    • 非线程安全
java
1// LinkedList核心源码简化版
2public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E> {
3 // 链表长度
4 transient int size;
5 // 头节点
6 transient Node<E> first;
7 // 尾节点
8 transient Node<E> last;
9
10 // 节点类
11 private static class Node<E> {
12 E item;
13 Node<E> next;
14 Node<E> prev;
15
16 Node(Node<E> prev, E element, Node<E> next) {
17 this.item = element;
18 this.next = next;
19 this.prev = prev;
20 }
21 }
22
23 // 添加元素
24 public boolean add(E e) {
25 linkLast(e);
26 return true;
27 }
28
29 // 在末尾添加元素
30 void linkLast(E e) {
31 final Node<E> l = last;
32 final Node<E> newNode = new Node<>(l, e, null);
33 last = newNode;
34 if (l == null)
35 first = newNode;
36 else
37 l.next = newNode;
38 size++;
39 }
40
41 // 获取元素
42 public E get(int index) {
43 checkElementIndex(index);
44 return node(index).item;
45 }
46
47 // 根据索引获取节点
48 Node<E> node(int index) {
49 if (index < (size >> 1)) {
50 // 从头开始查找
51 Node<E> x = first;
52 for (int i = 0; i < index; i++)
53 x = x.next;
54 return x;
55 } else {
56 // 从尾开始查找
57 Node<E> x = last;
58 for (int i = size - 1; i > index; i--)
59 x = x.prev;
60 return x;
61 }
62 }
63}

3.3 Vector

  • 数据结构:动态数组(与ArrayList相似)
  • 特点
    • 线程安全(方法被synchronized修饰)
    • 默认容量10,扩容策略是增长原始大小的100%
    • 性能较ArrayList略低(同步开销)
    • 已被ArrayList替代,仅为兼容旧代码保留

3.4 CopyOnWriteArrayList

  • 数据结构:内部持有一个数组,所有修改操作都在新数组上进行
  • 特点
    • 读操作不需要加锁,写操作创建数组副本
    • 适用于读多写少的场景
    • 迭代器不支持修改操作,但保证不抛出ConcurrentModificationException
    • 内存占用较高

4. Set实现类的底层原理

4.1 HashSet

  • 数据结构:基于HashMap实现
  • 内部实现
    • 元素作为HashMap的键存储,值使用一个静态Object对象
    • 使用元素的hashCode()和equals()方法判断重复
    • 无序(元素的顺序可能随时间变化)
    • 允许null元素
java
1// HashSet核心源码简化版
2public class HashSet<E> extends AbstractSet<E> implements Set<E> {
3 // 实际存储元素的HashMap
4 private transient HashMap<E,Object> map;
5
6 // 所有值对应的虚拟对象
7 private static final Object PRESENT = new Object();
8
9 // 构造函数
10 public HashSet() {
11 map = new HashMap<>();
12 }
13
14 // 添加元素
15 public boolean add(E e) {
16 return map.put(e, PRESENT) == null;
17 }
18
19 // 判断是否包含元素
20 public boolean contains(Object o) {
21 return map.containsKey(o);
22 }
23
24 // 移除元素
25 public boolean remove(Object o) {
26 return map.remove(o) == PRESENT;
27 }
28
29 // 迭代器
30 public Iterator<E> iterator() {
31 return map.keySet().iterator();
32 }
33
34 // 集合大小
35 public int size() {
36 return map.size();
37 }
38}

4.2 LinkedHashSet

  • 数据结构:基于LinkedHashMap实现的HashSet
  • 特点
    • 维护插入顺序
    • 性能略低于HashSet
    • 迭代性能优于HashSet

4.3 TreeSet

  • 数据结构:基于TreeMap实现的NavigableSet
  • 特点
    • 元素按照自然顺序或提供的比较器排序
    • 查找、添加和删除操作均为O(log n)
    • 不允许null元素
    • 非线程安全
java
1// TreeSet核心源码简化版
2public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E> {
3 // 实际存储的TreeMap
4 private transient NavigableMap<E,Object> m;
5
6 // 所有值对应的虚拟对象
7 private static final Object PRESENT = new Object();
8
9 // 构造函数
10 public TreeSet() {
11 this.m = new TreeMap<>();
12 }
13
14 public TreeSet(Comparator<? super E> comparator) {
15 this.m = new TreeMap<>(comparator);
16 }
17
18 // 添加元素
19 public boolean add(E e) {
20 return m.put(e, PRESENT) == null;
21 }
22
23 // 第一个元素
24 public E first() {
25 return m.firstKey();
26 }
27
28 // 最后一个元素
29 public E last() {
30 return m.lastKey();
31 }
32}

5. Map实现类的底层原理

5.1 HashMap

  • 数据结构:哈希表 + 链表/红黑树
  • 内部实现
    • 基于数组 + 链表组成的哈希桶,链表长度超过阈值(8)时转为红黑树
    • 默认初始容量为16,负载因子为0.75
    • 扩容时容量翻倍
    • 非线程安全
    • Java 8以后引入红黑树优化长链表性能
java
1// HashMap核心源码简化版
2public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
3 // 哈希桶数组
4 transient Node<K,V>[] table;
5
6 // 键值对数量
7 transient int size;
8
9 // 扩容阈值
10 int threshold;
11
12 // 负载因子
13 final float loadFactor;
14
15 // 默认初始容量
16 static final int DEFAULT_INITIAL_CAPACITY = 16;
17
18 // 默认负载因子
19 static final float DEFAULT_LOAD_FACTOR = 0.75f;
20
21 // 树化阈值
22 static final int TREEIFY_THRESHOLD = 8;
23
24 // 基本节点
25 static class Node<K,V> implements Map.Entry<K,V> {
26 final int hash;
27 final K key;
28 V value;
29 Node<K,V> next;
30
31 // 构造函数
32 Node(int hash, K key, V value, Node<K,V> next) {
33 this.hash = hash;
34 this.key = key;
35 this.value = value;
36 this.next = next;
37 }
38 }
39
40 // 放入键值对
41 public V put(K key, V value) {
42 return putVal(hash(key), key, value, false, true);
43 }
44
45 // 获取键对应的值
46 public V get(Object key) {
47 Node<K,V> e = getNode(hash(key), key);
48 return e == null ? null : e.value;
49 }
50}

HashMap的哈希冲突解决

  1. 链地址法:相同哈希值的元素放在同一链表
  2. 红黑树优化:链表长度超过阈值时转换为红黑树
  3. 哈希函数优化:减少哈希冲突

5.2 LinkedHashMap

  • 数据结构:哈希表 + 双向链表
  • 特点
    • 继承自HashMap,增加了双向链表维护元素顺序
    • 可按插入顺序或访问顺序排序(构造时指定)
    • 适合构建LRU缓存
    • 迭代性能优于HashMap

5.3 TreeMap

  • 数据结构:红黑树
  • 特点
    • 基于红黑树(自平衡二叉查找树)实现
    • 键按自然顺序或提供的比较器排序
    • 查找、添加和删除操作均为O(log n)
    • 非线程安全

5.4 ConcurrentHashMap

  • 数据结构:分段锁数组(Java 7) / 哈希表+CAS+Synchronized(Java 8+)

  • Java 7实现

    • 数据分为多个Segment,每个Segment独立加锁
    • 每个Segment类似一个独立的HashMap
    • 读操作无锁,写操作仅锁定对应段
    • 默认16个Segment
  • Java 8及以后实现

    • 摒弃了Segment设计,直接使用Node数组
    • 使用CAS和synchronized保证线程安全
    • 锁粒度更细,只锁定当前桶
    • 支持红黑树优化

5.5 Hashtable

  • 数据结构:哈希表
  • 特点
    • 线程安全(方法被synchronized修饰)
    • 不允许null键和null值
    • 性能不如ConcurrentHashMap
    • 已被更高效的实现替代

6. Queue/Deque实现类的底层原理

6.1 ArrayDeque

  • 数据结构:循环数组
  • 特点
    • 实现了Deque接口,可用作栈或队列
    • 无固定容量限制,自动扩容
    • 不允许null元素
    • 作为栈比Stack性能更好,作为队列比LinkedList性能更好
    • 非线程安全

6.2 PriorityQueue

  • 数据结构:基于堆(完全二叉树)
  • 特点
    • 元素按优先级排序(自然顺序或比较器)
    • 堆顶元素始终是最小元素
    • 插入和删除的时间复杂度为O(log n)
    • 非线程安全

6.3 LinkedList作为Queue/Deque

  • LinkedList同时实现了List和Deque接口
  • 可以用作队列、栈或双端队列
  • 链表结构使其在两端增删元素的性能很好

7. 并发集合类

Java提供了多种线程安全的集合实现:

7.1 ConcurrentHashMap

  • 替代Hashtable的高效并发Map实现
  • 读操作完全并发,写操作局部锁定

7.2 CopyOnWriteArrayList/CopyOnWriteArraySet

  • 适用于读多写少的场景
  • 修改操作通过复制整个数组实现
  • 迭代过程中不会抛出ConcurrentModificationException

7.3 ConcurrentSkipListMap/ConcurrentSkipListSet

  • 基于跳表实现的并发排序Map/Set
  • 替代TreeMap/TreeSet的并发版本
  • 所有操作都是线程安全的

8. 集合工具类

8.1 Collections类

提供了操作集合的静态方法:

  • 同步包装器:synchronizedList(), synchronizedMap()
  • 不可变包装器:unmodifiableList(), unmodifiableMap()
  • 类型安全包装器:checkedList(), checkedMap()
  • 单例集合:singletonList(), singletonMap()
  • 空集合:emptyList(), emptyMap()
  • 排序和查找:sort(), binarySearch()
  • 其他操作:reverse(), shuffle(), min(), max()

8.2 Arrays类

提供了操作数组的静态方法:

  • 数组与集合转换:asList()
  • 排序和查找:sort(), binarySearch()
  • 复制和填充:copyOf(), fill()
  • 比较:equals(), compare()
  • 流操作:stream()(Java 8+)

9. 集合框架的性能比较

集合类随机访问插入/删除查找内存占用有序性
ArrayListO(1)O(n)*O(n)插入顺序
LinkedListO(n)O(1)**O(n)插入顺序
HashSet不支持O(1)O(1)无序
LinkedHashSet不支持O(1)O(1)插入顺序
TreeSet不支持O(log n)O(log n)排序
HashMapO(1)O(1)O(1)无序
LinkedHashMapO(1)O(1)O(1)可选
TreeMapO(log n)O(log n)O(log n)排序

*: 末尾插入为O(1),中间插入需要移动元素
**: 需要先找到位置,实际复杂度取决于插入位置

10. 选择合适的集合类

根据需求选择合适的集合实现:

  1. 需要按索引访问元素:ArrayList
  2. 需要频繁插入、删除元素:LinkedList
  3. 需要保证元素唯一性:HashSet
  4. 需要保证元素唯一且有序:TreeSet
  5. 需要键值对映射:HashMap
  6. 需要键值对且保持插入顺序:LinkedHashMap
  7. 需要键值对且按键排序:TreeMap
  8. 需要线程安全的Map:ConcurrentHashMap
  9. 需要优先级队列:PriorityQueue
  10. 需要双端队列:ArrayDeque

40. Java中的IO模型有哪些?NIO如何实现多路复用?

Java提供了多种I/O模型来处理输入/输出操作,每种模型都有其特定的用途和性能特点。理解这些I/O模型对于开发高性能的Java应用至关重要。

1. Java I/O模型概览

Java中主要有四种I/O模型:

1.1 BIO (Blocking I/O)

  • 传统的阻塞式I/O模型
  • 线程发起I/O操作后阻塞等待完成
  • 简单直观但扩展性较差

1.2 NIO (Non-blocking I/O)

  • 非阻塞I/O,基于选择器的多路复用
  • 单线程可同时处理多个连接
  • 提高了资源利用率和系统吞吐量

1.3 AIO (Asynchronous I/O)

  • 异步I/O,也称为NIO.2
  • 操作系统完成I/O操作后通知应用程序
  • 真正的异步非阻塞I/O模型

1.4 IO多路复用

  • 底层依赖操作系统的select/poll/epoll机制
  • NIO通过Selector实现了Java层面的多路复用
  • 允许单线程监控多个文件描述符

2. BIO (Blocking I/O)

2.1 工作原理

  • 一个连接一个线程处理
  • I/O操作是同步阻塞的
  • 线程在执行读/写操作时被阻塞,直到操作完成

2.2 代码示例

java
1// BIO服务器示例
2ServerSocket serverSocket = new ServerSocket(8080);
3while (true) {
4 // 阻塞等待客户端连接
5 Socket clientSocket = serverSocket.accept();
6
7 // 为每个客户端创建一个线程处理
8 new Thread(() -> {
9 try {
10 BufferedReader reader = new BufferedReader(
11 new InputStreamReader(clientSocket.getInputStream()));
12 PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
13
14 String line;
15 // 阻塞读取客户端数据
16 while ((line = reader.readLine()) != null) {
17 writer.println("Echo: " + line);
18 }
19 } catch (IOException e) {
20 e.printStackTrace();
21 } finally {
22 try {
23 clientSocket.close();
24 } catch (IOException e) {
25 e.printStackTrace();
26 }
27 }
28 }).start();
29}

2.3 BIO的缺点

  • 连接数增加时,需要创建大量线程
  • 线程资源消耗大
  • 线程阻塞导致低效,每个连接无论活跃与否都占用一个线程
  • 适合连接数少且稳定的场景

3. NIO (Non-blocking I/O)

3.1 核心组件

  • Buffer (缓冲区)

    • 数据的容器,直接与通道交互
    • 常用实现有ByteBuffer, CharBuffer, IntBuffer等
    • 提供了高效的数据读写操作
  • Channel (通道)

    • 双向数据通道,支持读取和写入
    • 常用实现有FileChannel, SocketChannel, ServerSocketChannel等
    • 可以注册到Selector上进行多路复用
  • Selector (选择器)

    • 允许单线程处理多个Channel
    • 基于事件驱动模型
    • 通过select()方法监控注册的Channel状态

3.2 NIO工作原理

  1. 创建一个Selector
  2. 将Channel注册到Selector上,关注特定事件
  3. 调用Selector的select()方法阻塞等待事件发生
  4. 处理已就绪的事件
  5. 重复步骤3-4

3.3 代码示例

java
1// NIO服务器示例
2// 创建Selector
3Selector selector = Selector.open();
4
5// 创建ServerSocketChannel
6ServerSocketChannel serverChannel = ServerSocketChannel.open();
7serverChannel.bind(new InetSocketAddress(8080));
8serverChannel.configureBlocking(false); // 设置为非阻塞模式
9
10// 注册到Selector
11serverChannel.register(selector, SelectionKey.OP_ACCEPT);
12
13while (true) {
14 // 阻塞等待事件,返回准备就绪的通道数
15 int readyChannels = selector.select();
16 if (readyChannels == 0) continue;
17
18 // 获取就绪事件
19 Set<SelectionKey> selectedKeys = selector.selectedKeys();
20 Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
21
22 while (keyIterator.hasNext()) {
23 SelectionKey key = keyIterator.next();
24
25 if (key.isAcceptable()) {
26 // 处理接受连接事件
27 ServerSocketChannel server = (ServerSocketChannel) key.channel();
28 SocketChannel client = server.accept();
29 client.configureBlocking(false);
30 // 注册读事件
31 client.register(selector, SelectionKey.OP_READ);
32 } else if (key.isReadable()) {
33 // 处理读事件
34 SocketChannel client = (SocketChannel) key.channel();
35 ByteBuffer buffer = ByteBuffer.allocate(1024);
36 int bytesRead = client.read(buffer);
37
38 if (bytesRead > 0) {
39 buffer.flip();
40 client.write(buffer);
41 buffer.clear();
42 } else if (bytesRead == -1) {
43 // 客户端断开连接
44 client.close();
45 }
46 }
47
48 // 从集合中移除已处理的事件
49 keyIterator.remove();
50 }
51}

3.4 Buffer的基本操作

java
1// 创建缓冲区
2ByteBuffer buffer = ByteBuffer.allocate(1024);
3
4// 写入数据
5buffer.put("Hello".getBytes());
6
7// 切换到读模式
8buffer.flip();
9
10// 读取数据
11byte[] data = new byte[buffer.limit()];
12buffer.get(data);
13
14// 清空缓冲区,准备写入
15buffer.clear();

3.5 Buffer的三个重要属性

  • capacity:缓冲区的总容量
  • position:当前读写位置
  • limit:可读写的上限

3.6 Buffer的主要方法

  • flip():从写模式切换到读模式
  • rewind():重置position到0,保持limit不变
  • clear():清空整个缓冲区
  • compact():清空已读数据,保留未读数据
  • mark()/reset():标记当前位置/恢复到标记位置

4. NIO的多路复用机制

4.1 多路复用概念

多路复用是指使用一个线程监听多个连接(Channel),只有当连接上有数据可以非阻塞地进行I/O操作时,才对这些连接进行操作,避免同步阻塞造成的线程资源浪费。

4.2 实现原理

  1. 事件注册:将Channel注册到Selector,关联感兴趣的事件
  2. 事件监听:Selector监听所有注册的Channel
  3. 事件分发:当Channel有事件发生时,Selector通知应用程序处理

4.3 可注册的事件类型

  • SelectionKey.OP_READ:Channel可读
  • SelectionKey.OP_WRITE:Channel可写
  • SelectionKey.OP_CONNECT:客户端Channel连接完成
  • SelectionKey.OP_ACCEPT:服务器Channel接受新连接

4.4 SelectionKey

SelectionKey代表Channel在Selector中的注册关系,包含:

  • 对应的Channel和Selector
  • 感兴趣的事件集合
  • 已就绪的事件集合
  • 可附加的对象(attachment)
java
1// SelectionKey操作示例
2SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
3
4// 修改关注的事件
5key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
6
7// 获取已就绪事件
8int readyOps = key.readyOps();
9boolean isReadable = key.isReadable(); // 等价于(readyOps & SelectionKey.OP_READ) != 0
10
11// 附加对象
12key.attach(new ClientSession());
13ClientSession session = (ClientSession)key.attachment();

4.5 Selector的核心方法

  • select():阻塞选择准备就绪的通道
  • select(long timeout):最多阻塞timeout毫秒
  • selectNow():非阻塞选择
  • wakeup():唤醒阻塞在select()的线程
  • close():关闭选择器

4.6 NIO多路复用的底层实现

Java NIO的多路复用在不同操作系统上有不同实现:

  • Windows:基于select实现
  • Linux:2.6以前使用poll,2.6以后使用epoll
  • macOS/BSD:使用kqueue

Selector的底层调用

  1. Linux上创建Selector实际上是创建epoll实例
  2. 将Channel注册到Selector相当于调用epoll_ctl
  3. select操作对应epoll_wait系统调用

5. AIO (Asynchronous I/O)

5.1 概念

  • Java 7引入的NIO.2包提供了异步I/O功能
  • 基于回调机制:应用程序发起I/O操作后立即返回
  • 操作系统完成I/O后通知应用程序
  • 真正的异步非阻塞I/O模型

5.2 核心组件

  • AsynchronousChannel:异步通道接口
  • AsynchronousSocketChannel:异步Socket通道
  • AsynchronousServerSocketChannel:异步服务器Socket通道
  • AsynchronousFileChannel:异步文件通道
  • CompletionHandler:操作完成时的回调接口

5.3 工作原理

  1. 应用程序发起异步I/O操作
  2. 指定CompletionHandler回调
  3. I/O操作立即返回
  4. 操作系统完成I/O后调用回调函数

5.4 代码示例

java
1// AIO服务器示例
2AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
3serverChannel.bind(new InetSocketAddress(8080));
4
5// 接受连接
6serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
7 @Override
8 public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
9 // 继续接受下一个连接
10 serverChannel.accept(null, this);
11
12 // 分配缓冲区
13 ByteBuffer buffer = ByteBuffer.allocate(1024);
14
15 // 异步读取
16 clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
17 @Override
18 public void completed(Integer bytesRead, ByteBuffer buffer) {
19 if (bytesRead > 0) {
20 buffer.flip();
21 // 异步写回
22 clientChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
23 @Override
24 public void completed(Integer bytesWritten, ByteBuffer buffer) {
25 if (buffer.hasRemaining()) {
26 // 继续写入剩余数据
27 clientChannel.write(buffer, buffer, this);
28 } else {
29 // 准备下一次读取
30 buffer.clear();
31 clientChannel.read(buffer, buffer, CompletionHandler.this);
32 }
33 }
34
35 @Override
36 public void failed(Throwable exc, ByteBuffer attachment) {
37 try {
38 clientChannel.close();
39 } catch (IOException e) {
40 e.printStackTrace();
41 }
42 }
43 });
44 }
45 }
46
47 @Override
48 public void failed(Throwable exc, ByteBuffer attachment) {
49 try {
50 clientChannel.close();
51 } catch (IOException e) {
52 e.printStackTrace();
53 }
54 }
55 });
56 }
57
58 @Override
59 public void failed(Throwable exc, Void attachment) {
60 exc.printStackTrace();
61 }
62});
63
64// 保持主线程不退出
65Thread.currentThread().join();

5.5 Future方式

除了使用CompletionHandler回调,AIO也支持Future风格:

java
1// 使用Future
2AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
3 Paths.get("test.txt"), StandardOpenOption.READ);
4
5ByteBuffer buffer = ByteBuffer.allocate(1024);
6Future<Integer> result = fileChannel.read(buffer, 0);
7
8// 可以执行其他操作...
9
10// 阻塞等待结果
11int bytesRead = result.get(); // 阻塞直到读取完成

6. 各种I/O模型的对比

特性BIONIOAIO
阻塞类型阻塞非阻塞异步非阻塞
编程复杂度简单复杂较复杂
调用方式同步同步(多路复用)异步
适用场景连接数少高并发连接I/O密集型
可靠性较高较高一般
吞吐量

7. Reactor模式与Proactor模式

7.1 Reactor模式

  • NIO采用的是Reactor模式
  • 应用程序通过Selector主动监听事件
  • 事件发生时,应用程序负责处理I/O操作
  • 同步非阻塞模型

Reactor模式的角色

  • Reactor:负责监听和分发事件
  • Handler:处理特定事件
  • Acceptor:处理客户端连接请求

7.2 Proactor模式

  • AIO采用的是Proactor模式
  • 应用程序发起异步I/O操作后立即返回
  • 系统完成实际I/O操作并通知应用程序
  • 异步非阻塞模型

Proactor模式的角色

  • Proactor:管理异步I/O操作
  • Completion Handler:处理异步I/O完成事件
  • Asynchronous Operation:表示异步操作

8. NIO的适用场景与实践

8.1 适用场景

  • 高并发连接服务器
  • 需要管理大量连接但活动连接较少的情况
  • 长连接应用(如聊天服务器)
  • 需要高吞吐量和低延迟的场景

8.2 NIO的局限性

  • 编程模型复杂
  • 调试困难
  • 在连接数较少时,BIO可能更简单高效
  • 依赖操作系统的I/O多路复用支持

8.3 常见问题及解决方案

  • 空轮询bug:早期JDK版本的Selector在某些Linux系统上会出现CPU 100%的bug

    • 解决:升级JDK或重建Selector
  • 内存泄漏:未正确关闭资源可能导致内存泄漏

    • 解决:使用try-with-resources或finally块确保资源关闭
  • 数据传输不完整:非阻塞模式下,read/write方法可能不会一次完成所有数据传输

    • 解决:循环处理,或使用更高级的缓冲区管理
  • 多线程访问:Selector和相关组件不是线程安全的

    • 解决:适当使用同步机制,或采用多Selector模型

8.4 NIO最佳实践

  1. Buffer管理

    • 正确处理Buffer的读写模式切换(flip/clear/compact)
    • 考虑使用直接缓冲区(DirectByteBuffer)提高性能
    • 注意缓冲区大小设置,避免过大或过小
  2. Channel管理

    • 确保正确关闭Channel
    • 适当配置Channel参数(如TCP参数)
    • 对于文件Channel,考虑使用内存映射或文件锁定
  3. Selector管理

    • 定期检查Selector的健康状态
    • 考虑多Selector多线程模型分担负载
    • 适当处理wakeup()机制
  4. 异常处理

    • 捕获和处理特定的I/O异常
    • 实现正确的错误恢复机制
    • 避免异常导致整个服务器崩溃

9. 基于Java NIO的开源框架

9.1 Netty

  • 高性能的NIO客户端/服务器框架
  • 简化了NIO编程模型
  • 提供了丰富的协议支持和编解码器
  • 广泛用于高性能网络应用开发

9.2 Apache MINA

  • 类似Netty的NIO框架
  • 提供了抽象的I/O API
  • 支持多种传输协议

9.3 Grizzly

  • Oracle开发的NIO框架
  • 是Glassfish服务器的网络层
  • 支持HTTP/HTTPS等协议

9.4 Eclipse Vert.x

  • 基于Netty的反应式应用平台
  • 提供了多语言API
  • 事件驱动、非阻塞的编程模型

10. 小结与展望

10.1 选择合适的I/O模型

  • BIO:简单应用,连接数少
  • NIO:高并发服务器,需要处理大量连接
  • AIO:需要真正异步I/O的场景,如大文件处理

10.2 未来趋势

  • 反应式编程模型的普及
  • 基于NIO的框架继续发展
  • 与Java异步编程模型(CompletableFuture, Flow API)的融合
  • 对新型I/O设备(如NVMe SSD)的优化支持

41. Java多线程编程的基本概念和实现方式有哪些?

多线程编程是Java语言的核心特性之一,它允许程序同时执行多个线程,提高程序的效率和响应性。理解多线程的基本概念和实现方式对于开发高效的Java应用至关重要。

1. 多线程基础概念

1.1 进程与线程的区别

  • 进程(Process)

    • 操作系统资源分配的基本单位
    • 拥有独立的内存空间
    • 进程间通信(IPC)相对复杂
  • 线程(Thread)

    • CPU调度的基本单位
    • 共享所属进程的内存空间
    • 线程间通信相对简单
    • 创建和销毁开销小于进程

1.2 多线程的优势

  1. 提高资源利用率:CPU空闲时可执行其他线程
  2. 提高响应性:耗时操作不会阻塞整个程序
  3. 简化程序设计:将复杂任务分解为多个并行执行的子任务

1.3 多线程的挑战

  1. 线程安全问题:共享资源访问冲突
  2. 死锁、活锁、饥饿:并发编程中的常见问题
  3. 难以调试:并发bug难以重现和定位
  4. 性能开销:线程创建、上下文切换的开销

2. 线程的生命周期

Java线程的生命周期包含以下状态,由Thread.State枚举定义:

  1. NEW:线程创建但未启动
  2. RUNNABLE:线程正在运行或等待CPU时间片
  3. BLOCKED:线程阻塞,等待获取监视器锁
  4. WAITING:线程无限期等待另一个线程执行特定操作
  5. TIMED_WAITING:线程等待另一个线程执行操作,但有超时限制
  6. TERMINATED:线程已执行完毕

线程状态转换图

1NEW
2 |
3 v
4 RUNNABLE <------+
5 / | \ |
6v v v |
7BLOCKED WAITING TIMED_WAITING
8 \ | /
9 \ | /
10 v v v
11 TERMINATED

3. 创建线程的方式

3.1 继承Thread类

java
1public class MyThread extends Thread {
2 @Override
3 public void run() {
4 // 线程执行代码
5 System.out.println("Thread running: " + Thread.currentThread().getName());
6 }
7
8 public static void main(String[] args) {
9 MyThread thread = new MyThread();
10 thread.start(); // 启动线程
11 }
12}

优点:简单直观 缺点:Java单继承限制,无法继承其他类

3.2 实现Runnable接口

java
1public class MyRunnable implements Runnable {
2 @Override
3 public void run() {
4 // 线程执行代码
5 System.out.println("Thread running: " + Thread.currentThread().getName());
6 }
7
8 public static void main(String[] args) {
9 Thread thread = new Thread(new MyRunnable());
10 thread.start(); // 启动线程
11
12 // 使用Lambda表达式(Java 8+)
13 Thread thread2 = new Thread(() -> {
14 System.out.println("Lambda thread running");
15 });
16 thread2.start();
17 }
18}

优点:可以继承其他类,更好地面向对象 缺点:无法直接获取线程执行结果

3.3 实现Callable接口和使用FutureTask

java
1public class MyCallable implements Callable<Integer> {
2 @Override
3 public Integer call() throws Exception {
4 // 计算并返回结果
5 int sum = 0;
6 for (int i = 1; i <= 100; i++) {
7 sum += i;
8 }
9 return sum;
10 }
11
12 public static void main(String[] args) throws Exception {
13 // 创建Callable实例
14 MyCallable callable = new MyCallable();
15 // 包装为FutureTask
16 FutureTask<Integer> futureTask = new FutureTask<>(callable);
17 // 创建线程执行任务
18 Thread thread = new Thread(futureTask);
19 thread.start();
20
21 // 获取计算结果
22 Integer result = futureTask.get(); // 阻塞等待结果
23 System.out.println("Result: " + result);
24 }
25}

优点:可以获取线程执行结果,可以抛出异常 缺点:使用较复杂

3.4 使用线程池

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3import java.util.concurrent.Future;
4
5public class ThreadPoolExample {
6 public static void main(String[] args) throws Exception {
7 // 创建固定大小的线程池
8 ExecutorService executor = Executors.newFixedThreadPool(5);
9
10 // 提交Runnable任务
11 executor.execute(() -> {
12 System.out.println("Runnable task executed");
13 });
14
15 // 提交Callable任务并获取结果
16 Future<Integer> future = executor.submit(() -> {
17 Thread.sleep(1000);
18 return 123;
19 });
20
21 Integer result = future.get();
22 System.out.println("Result: " + result);
23
24 // 关闭线程池
25 executor.shutdown();
26 }
27}

优点

  • 重用线程,减少创建和销毁线程的开销
  • 控制并发数,避免资源耗尽
  • 提供任务管理和执行服务

常见的线程池类型

  • FixedThreadPool:固定大小线程池
  • CachedThreadPool:可缓存线程池,自动调整大小
  • SingleThreadExecutor:单线程执行器
  • ScheduledThreadPool:定时任务线程池
  • ForkJoinPool:Fork/Join框架使用的线程池(Java 7+)

4. 线程同步与协作

4.1 线程同步

线程同步是确保多个线程安全访问共享资源的机制。

4.1.1 synchronized关键字

java
1// 同步方法
2public synchronized void syncMethod() {
3 // 同步代码
4}
5
6// 同步代码块
7public void method() {
8 synchronized(this) {
9 // 同步代码
10 }
11}
12
13// 同步静态方法(类锁)
14public static synchronized void staticSyncMethod() {
15 // 同步代码
16}
17
18// 同步类
19public void method() {
20 synchronized(MyClass.class) {
21 // 同步代码
22 }
23}

4.1.2 Lock接口

java
1import java.util.concurrent.locks.Lock;
2import java.util.concurrent.locks.ReentrantLock;
3
4public class LockExample {
5 private final Lock lock = new ReentrantLock();
6 private int count = 0;
7
8 public void increment() {
9 lock.lock(); // 获取锁
10 try {
11 count++;
12 } finally {
13 lock.unlock(); // 释放锁
14 }
15 }
16
17 // 支持中断的锁获取
18 public void incrementInterruptibly() throws InterruptedException {
19 lock.lockInterruptibly(); // 可中断锁
20 try {
21 count++;
22 } finally {
23 lock.unlock();
24 }
25 }
26
27 // 尝试获取锁
28 public boolean tryIncrement() {
29 if (lock.tryLock()) { // 尝试获取锁但不阻塞
30 try {
31 count++;
32 return true;
33 } finally {
34 lock.unlock();
35 }
36 }
37 return false;
38 }
39}

4.1.3 ReadWriteLock

java
1import java.util.concurrent.locks.ReadWriteLock;
2import java.util.concurrent.locks.ReentrantReadWriteLock;
3
4public class ReadWriteLockExample {
5 private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
6 private int data = 0;
7
8 public int readData() {
9 rwLock.readLock().lock(); // 获取读锁
10 try {
11 return data;
12 } finally {
13 rwLock.readLock().unlock(); // 释放读锁
14 }
15 }
16
17 public void writeData(int newData) {
18 rwLock.writeLock().lock(); // 获取写锁
19 try {
20 data = newData;
21 } finally {
22 rwLock.writeLock().unlock(); // 释放写锁
23 }
24 }
25}

4.1.4 volatile关键字

java
1public class VolatileExample {
2 private volatile boolean flag = false; // 保证可见性
3
4 public void setFlag() {
5 flag = true;
6 }
7
8 public void doWork() {
9 while (!flag) {
10 // 等待flag变为true
11 // 由于flag是volatile的,其他线程对flag的修改对本线程可见
12 }
13 // flag为true时继续执行
14 }
15}

4.2 线程协作

线程协作是线程之间相互配合完成任务的机制。

4.2.1 wait/notify/notifyAll

java
1public class WaitNotifyExample {
2 private final Object lock = new Object();
3 private boolean condition = false;
4
5 public void producer() {
6 synchronized (lock) {
7 while (condition) {
8 try {
9 lock.wait(); // 等待条件变化
10 } catch (InterruptedException e) {
11 Thread.currentThread().interrupt();
12 }
13 }
14 condition = true;
15 // 生产数据
16 lock.notifyAll(); // 通知等待的消费者
17 }
18 }
19
20 public void consumer() {
21 synchronized (lock) {
22 while (!condition) {
23 try {
24 lock.wait(); // 等待条件变化
25 } catch (InterruptedException e) {
26 Thread.currentThread().interrupt();
27 }
28 }
29 condition = false;
30 // 消费数据
31 lock.notifyAll(); // 通知等待的生产者
32 }
33 }
34}

4.2.2 Condition接口

java
1import java.util.concurrent.locks.Condition;
2import java.util.concurrent.locks.Lock;
3import java.util.concurrent.locks.ReentrantLock;
4
5public class ConditionExample {
6 private final Lock lock = new ReentrantLock();
7 private final Condition notFull = lock.newCondition();
8 private final Condition notEmpty = lock.newCondition();
9 private final Object[] items = new Object[100];
10 private int count = 0, putIndex = 0, takeIndex = 0;
11
12 public void put(Object item) throws InterruptedException {
13 lock.lock();
14 try {
15 while (count == items.length) {
16 notFull.await(); // 队列满了,等待消费者消费
17 }
18 items[putIndex] = item;
19 putIndex = (putIndex + 1) % items.length;
20 count++;
21 notEmpty.signal(); // 通知消费者可以消费了
22 } finally {
23 lock.unlock();
24 }
25 }
26
27 public Object take() throws InterruptedException {
28 lock.lock();
29 try {
30 while (count == 0) {
31 notEmpty.await(); // 队列空了,等待生产者生产
32 }
33 Object item = items[takeIndex];
34 items[takeIndex] = null;
35 takeIndex = (takeIndex + 1) % items.length;
36 count--;
37 notFull.signal(); // 通知生产者可以生产了
38 return item;
39 } finally {
40 lock.unlock();
41 }
42 }
43}

4.2.3 CountDownLatch

java
1import java.util.concurrent.CountDownLatch;
2
3public class CountDownLatchExample {
4 public static void main(String[] args) throws InterruptedException {
5 CountDownLatch latch = new CountDownLatch(3); // 初始计数为3
6
7 // 创建3个工作线程
8 for (int i = 0; i < 3; i++) {
9 final int num = i;
10 new Thread(() -> {
11 try {
12 System.out.println("Worker " + num + " is working");
13 Thread.sleep(1000); // 模拟工作
14 latch.countDown(); // 完成工作,计数减1
15 System.out.println("Worker " + num + " finished");
16 } catch (InterruptedException e) {
17 Thread.currentThread().interrupt();
18 }
19 }).start();
20 }
21
22 System.out.println("Main thread waiting for all workers");
23 latch.await(); // 主线程等待计数器归零
24 System.out.println("All workers finished, main thread continues");
25 }
26}

4.2.4 CyclicBarrier

java
1import java.util.concurrent.CyclicBarrier;
2
3public class CyclicBarrierExample {
4 public static void main(String[] args) {
5 // 创建栅栏,所有线程到达后执行指定任务
6 CyclicBarrier barrier = new CyclicBarrier(3, () -> {
7 System.out.println("All threads have reached the barrier, continue processing");
8 });
9
10 for (int i = 0; i < 3; i++) {
11 final int num = i;
12 new Thread(() -> {
13 try {
14 System.out.println("Thread " + num + " is working");
15 Thread.sleep(1000); // 模拟工作
16 System.out.println("Thread " + num + " reached the barrier");
17 barrier.await(); // 等待其他线程到达
18 System.out.println("Thread " + num + " continues after barrier");
19 } catch (Exception e) {
20 e.printStackTrace();
21 }
22 }).start();
23 }
24 }
25}

4.2.5 Semaphore

java
1import java.util.concurrent.Semaphore;
2
3public class SemaphoreExample {
4 public static void main(String[] args) {
5 // 创建信号量,只允许3个线程同时访问资源
6 Semaphore semaphore = new Semaphore(3);
7
8 for (int i = 0; i < 6; i++) {
9 final int num = i;
10 new Thread(() -> {
11 try {
12 System.out.println("Thread " + num + " is waiting for permit");
13 semaphore.acquire(); // 获取许可
14 System.out.println("Thread " + num + " got permit");
15 Thread.sleep(1000); // 使用资源
16 } catch (InterruptedException e) {
17 Thread.currentThread().interrupt();
18 } finally {
19 System.out.println("Thread " + num + " releases permit");
20 semaphore.release(); // 释放许可
21 }
22 }).start();
23 }
24 }
25}

4.2.6 Phaser

java
1import java.util.concurrent.Phaser;
2
3public class PhaserExample {
4 public static void main(String[] args) {
5 Phaser phaser = new Phaser(3); // 注册3个参与者
6
7 for (int i = 0; i < 3; i++) {
8 final int num = i;
9 new Thread(() -> {
10 // 阶段1
11 System.out.println("Thread " + num + " completing phase 1");
12 phaser.arriveAndAwaitAdvance(); // 等待所有线程完成阶段1
13
14 // 阶段2
15 System.out.println("Thread " + num + " completing phase 2");
16 phaser.arriveAndAwaitAdvance(); // 等待所有线程完成阶段2
17
18 // 阶段3
19 System.out.println("Thread " + num + " completing phase 3");
20 phaser.arriveAndDeregister(); // 完成并退出Phaser
21 }).start();
22 }
23 }
24}

5. 线程安全的集合类

Java提供了多种线程安全的集合类,适用于不同场景:

5.1 同步集合类

  • Vector:线程安全的ArrayList
  • Hashtable:线程安全的HashMap
  • Collections.synchronizedXxx():将普通集合包装为同步集合
java
1List<String> syncList = Collections.synchronizedList(new ArrayList<>());
2Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

5.2 并发集合类

  • ConcurrentHashMap:分段锁实现的线程安全HashMap
  • CopyOnWriteArrayList:写时复制的ArrayList
  • CopyOnWriteArraySet:基于CopyOnWriteArrayList的Set实现
  • ConcurrentSkipListMap:基于跳表的排序Map
  • ConcurrentSkipListSet:基于ConcurrentSkipListMap的排序Set
java
1Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
2List<String> concurrentList = new CopyOnWriteArrayList<>();

5.3 阻塞队列

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的可选有界阻塞队列
  • PriorityBlockingQueue:基于优先级堆的无界阻塞队列
  • DelayQueue:延迟元素队列
  • SynchronousQueue:无缓冲的阻塞队列,直接交付
java
1BlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(100);
2
3// 生产者
4void producer() throws InterruptedException {
5 Task task = createTask();
6 taskQueue.put(task); // 如果队列满,会阻塞
7}
8
9// 消费者
10void consumer() throws InterruptedException {
11 Task task = taskQueue.take(); // 如果队列空,会阻塞
12 processTask(task);
13}

6. ThreadLocal

ThreadLocal提供线程局部变量,每个线程都有自己独立的副本,实现线程隔离。

java
1public class ThreadLocalExample {
2 // 创建ThreadLocal变量
3 private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
4 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
5
6 public static String formatDate(Date date) {
7 // 获取当前线程的SimpleDateFormat
8 return dateFormatThreadLocal.get().format(date);
9 }
10
11 public static void main(String[] args) {
12 // 多个线程使用同一个方法,但每个线程都有自己的SimpleDateFormat实例
13 for (int i = 0; i < 10; i++) {
14 new Thread(() -> {
15 System.out.println(formatDate(new Date()));
16 // 使用完毕后清除,防止内存泄漏
17 dateFormatThreadLocal.remove();
18 }).start();
19 }
20 }
21}

注意:使用ThreadLocal时要注意及时调用remove()方法,防止内存泄漏。

7. 线程安全性

7.1 线程安全的定义

线程安全指的是在多线程环境下,对共享资源的操作不会产生不可预期的结果。

7.2 实现线程安全的方法

  1. 不可变性:创建不可变对象,如String、Integer等
  2. 互斥同步:使用synchronized、Lock等同步访问共享资源
  3. 非阻塞同步:使用CAS(Compare And Swap)等原子操作
  4. 避免共享:使用ThreadLocal等确保资源不共享
  5. 线程安全类:使用现成的线程安全集合和工具类

7.3 线程安全的实例设计

java
1// 不可变对象
2public final class ImmutableValue {
3 private final int value;
4
5 public ImmutableValue(int value) {
6 this.value = value;
7 }
8
9 public int getValue() {
10 return value;
11 }
12
13 public ImmutableValue add(int valueToAdd) {
14 return new ImmutableValue(this.value + valueToAdd);
15 }
16}
17
18// 使用原子类
19import java.util.concurrent.atomic.AtomicInteger;
20
21public class AtomicCounter {
22 private AtomicInteger count = new AtomicInteger(0);
23
24 public void increment() {
25 count.incrementAndGet(); // 原子操作,无需额外同步
26 }
27
28 public int getCount() {
29 return count.get();
30 }
31}

8. 并发编程的最佳实践

  1. 优先使用不可变对象:避免同步问题
  2. 减少锁的粒度:只锁定必要的代码块
  3. 避免在循环中加锁:尽量将锁移到循环外
  4. 避免过度同步:会降低并发性能
  5. 优先使用并发集合:而非同步包装器
  6. 正确使用wait/notify:总是在循环中检查等待条件
  7. 避免嵌套锁:降低死锁风险
  8. 设置合理的线程池大小:考虑CPU核心数和任务类型
  9. 使用线程池执行异步任务:优于手动创建线程
  10. 使用非阻塞算法:提高并发性能

9. Java 8+中的并发增强

9.1 CompletableFuture

java
1import java.util.concurrent.CompletableFuture;
2
3public class CompletableFutureExample {
4 public static void main(String[] args) throws Exception {
5 // 异步执行任务
6 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
7 try {
8 Thread.sleep(1000);
9 } catch (InterruptedException e) {
10 Thread.currentThread().interrupt();
11 }
12 return "Hello";
13 });
14
15 // 对结果进行转换
16 CompletableFuture<String> greetingFuture = future.thenApply(s -> s + " World");
17
18 // 消费结果
19 greetingFuture.thenAccept(System.out::println);
20
21 // 组合两个Future
22 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
23 CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
24
25 CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
26 System.out.println(combined.get()); // Hello World
27 }
28}

9.2 并行流(Parallel Streams)

java
1import java.util.Arrays;
2import java.util.List;
3
4public class ParallelStreamExample {
5 public static void main(String[] args) {
6 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
7
8 // 串行流
9 long count1 = numbers.stream()
10 .filter(n -> n % 2 == 0)
11 .count();
12
13 // 并行流
14 long count2 = numbers.parallelStream()
15 .filter(n -> n % 2 == 0)
16 .count();
17 }
18}

9.3 StampedLock

java
1import java.util.concurrent.locks.StampedLock;
2
3public class StampedLockExample {
4 private double x, y;
5 private final StampedLock lock = new StampedLock();
6
7 // 写锁
8 public void move(double deltaX, double deltaY) {
9 long stamp = lock.writeLock();
10 try {
11 x += deltaX;
12 y += deltaY;
13 } finally {
14 lock.unlockWrite(stamp);
15 }
16 }
17
18 // 乐观读
19 public double distanceFromOrigin() {
20 // 乐观读取(无锁)
21 long stamp = lock.tryOptimisticRead();
22 double currentX = x, currentY = y;
23
24 // 检查读期间是否有修改
25 if (!lock.validate(stamp)) {
26 // 悲观读取(获取读锁)
27 stamp = lock.readLock();
28 try {
29 currentX = x;
30 currentY = y;
31 } finally {
32 lock.unlockRead(stamp);
33 }
34 }
35
36 return Math.sqrt(currentX * currentX + currentY * currentY);
37 }
38}

9.4 CountedCompleter

java
1import java.util.concurrent.CountedCompleter;
2import java.util.concurrent.ForkJoinPool;
3
4public class CountedCompleterExample {
5 public static void main(String[] args) {
6 int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
7 Sum sum = new Sum(null, array, 0, array.length);
8 ForkJoinPool.commonPool().invoke(sum);
9 System.out.println("Sum: " + sum.result);
10 }
11
12 static class Sum extends CountedCompleter<Void> {
13 final int[] array;
14 final int lo, hi;
15 int result;
16
17 Sum(Sum parent, int[] array, int lo, int hi) {
18 super(parent);
19 this.array = array;
20 this.lo = lo;
21 this.hi = hi;
22 }
23
24 @Override
25 public void compute() {
26 if (hi - lo > 1) {
27 int mid = (lo + hi) >>> 1;
28 // 创建子任务
29 Sum left = new Sum(this, array, lo, mid);
30 Sum right = new Sum(this, array, mid, hi);
31 // 设置待完成的子任务数量
32 setPendingCount(2);
33 // 启动子任务
34 left.fork();
35 right.fork();
36 } else {
37 if (hi > lo)
38 result = array[lo];
39 tryComplete(); // 尝试完成当前任务
40 }
41 }
42
43 @Override
44 public void onCompletion(CountedCompleter<?> caller) {
45 if (caller != this) {
46 Sum child = (Sum)caller;
47 result += child.result;
48 }
49 }
50 }
51}

10. 多线程调试与故障排除

10.1 常见并发问题

  1. 竞态条件:多线程访问共享资源时结果依赖于线程执行顺序
  2. 死锁:两个或多个线程互相等待对方持有的锁
  3. 活锁:线程不断重试失败的操作,但互相让步而无法前进
  4. 线程饥饿:线程无法获取所需资源而无法继续执行
  5. 过度同步:同步过多导致性能下降

10.2 死锁检测与解决

死锁发生的四个必要条件:

  1. 互斥:资源不能被共享
  2. 持有并等待:线程持有资源同时等待其他资源
  3. 不可剥夺:资源只能由持有者自愿释放
  4. 循环等待:存在循环的资源依赖链

解决死锁的方法:

  1. 避免嵌套锁:按固定顺序获取锁
  2. 使用tryLock():带超时的锁获取
  3. 使用无锁数据结构:避免使用显式锁
  4. 检测与恢复:定期检查死锁并回滚操作

死锁示例及修复

java
1// 死锁示例
2public class DeadlockExample {
3 private final Object resource1 = new Object();
4 private final Object resource2 = new Object();
5
6 public void method1() {
7 synchronized(resource1) {
8 System.out.println("Thread 1: Holding resource 1...");
9 try { Thread.sleep(100); } catch (InterruptedException e) {}
10
11 synchronized(resource2) {
12 System.out.println("Thread 1: Holding resource 1 & 2...");
13 }
14 }
15 }
16
17 public void method2() {
18 synchronized(resource2) { // 锁获取顺序与method1相反,可能导致死锁
19 System.out.println("Thread 2: Holding resource 2...");
20 try { Thread.sleep(100); } catch (InterruptedException e) {}
21
22 synchronized(resource1) {
23 System.out.println("Thread 2: Holding resource 2 & 1...");
24 }
25 }
26 }
27
28 // 修复:保持一致的锁获取顺序
29 public void method2Fixed() {
30 synchronized(resource1) { // 与method1相同的锁获取顺序
31 System.out.println("Thread 2: Holding resource 1...");
32 try { Thread.sleep(100); } catch (InterruptedException e) {}
33
34 synchronized(resource2) {
35 System.out.println("Thread 2: Holding resource 1 & 2...");
36 }
37 }
38 }
39}

10.3 线程转储(Thread Dump)分析

线程转储包含所有线程的状态信息,是诊断线程问题的重要工具。

获取线程转储的方法:

  1. jstack <pid>命令
  2. 在JVM中发送SIGQUIT信号(Linux/Unix)
  3. 在JVisualVM、JConsole等工具中查看

线程状态分析:

  • RUNNABLE:正在运行或等待系统资源
  • BLOCKED:等待进入同步块
  • WAITING/TIMED_WAITING:等待其他线程操作
  • PARKED:通过LockSupport.park()被挂起

42. 设计模式在Java中的应用有哪些?

设计模式是软件开发中解决常见问题的可重用解决方案,是经过实践检验的最佳实践。Java作为一种面向对象编程语言,广泛应用了各类设计模式。理解这些模式能帮助开发者编写更灵活、可维护的代码。

1. 创建型模式(Creational Patterns)

创建型模式关注对象的创建机制,通过控制对象创建过程来减少系统的复杂性。

1.1 单例模式(Singleton Pattern)

确保一个类只有一个实例,并提供一个全局访问点。

实现方式

  1. 饿汉式:类加载时创建实例
java
1public class EagerSingleton {
2 // 在类加载时就创建实例
3 private static final EagerSingleton INSTANCE = new EagerSingleton();
4
5 // 私有构造函数,防止外部实例化
6 private EagerSingleton() {}
7
8 public static EagerSingleton getInstance() {
9 return INSTANCE;
10 }
11}
  1. 懒汉式:延迟创建实例,线程安全版本
java
1public class LazySingleton {
2 // 声明但不初始化实例
3 private static volatile LazySingleton instance;
4
5 private LazySingleton() {}
6
7 // 双重检查锁定
8 public static LazySingleton getInstance() {
9 if (instance == null) {
10 synchronized (LazySingleton.class) {
11 if (instance == null) {
12 instance = new LazySingleton();
13 }
14 }
15 }
16 return instance;
17 }
18}
  1. 静态内部类:延迟加载且线程安全
java
1public class StaticInnerClassSingleton {
2 private StaticInnerClassSingleton() {}
3
4 private static class SingletonHolder {
5 private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
6 }
7
8 public static StaticInnerClassSingleton getInstance() {
9 return SingletonHolder.INSTANCE;
10 }
11}
  1. 枚举实现:最简洁的实现方式,自动线程安全且防止序列化问题
java
1public enum EnumSingleton {
2 INSTANCE;
3
4 // 可以添加方法和属性
5 public void doSomething() {
6 // 方法实现
7 }
8}

应用:Spring中的Bean默认是单例的,Java运行时中的Runtime类等。

1.2 工厂模式(Factory Pattern)

简单工厂模式:由一个工厂类根据传入的参数决定创建哪一种产品类的实例。

java
1public class ShapeFactory {
2 public Shape createShape(String type) {
3 if (type == null) {
4 return null;
5 }
6 if (type.equalsIgnoreCase("CIRCLE")) {
7 return new Circle();
8 } else if (type.equalsIgnoreCase("RECTANGLE")) {
9 return new Rectangle();
10 } else if (type.equalsIgnoreCase("SQUARE")) {
11 return new Square();
12 }
13 return null;
14 }
15}

工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪个类。

java
1// 抽象工厂
2public abstract class ShapeFactory {
3 public abstract Shape createShape();
4}
5
6// 具体工厂
7public class CircleFactory extends ShapeFactory {
8 @Override
9 public Shape createShape() {
10 return new Circle();
11 }
12}
13
14public class RectangleFactory extends ShapeFactory {
15 @Override
16 public Shape createShape() {
17 return new Rectangle();
18 }
19}

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

java
1// 抽象工厂
2public interface GUIFactory {
3 Button createButton();
4 Checkbox createCheckbox();
5}
6
7// 具体工厂
8public class WindowsFactory implements GUIFactory {
9 @Override
10 public Button createButton() {
11 return new WindowsButton();
12 }
13
14 @Override
15 public Checkbox createCheckbox() {
16 return new WindowsCheckbox();
17 }
18}
19
20public class MacFactory implements GUIFactory {
21 @Override
22 public Button createButton() {
23 return new MacButton();
24 }
25
26 @Override
27 public Checkbox createCheckbox() {
28 return new MacCheckbox();
29 }
30}

应用:Java Collections中的集合工厂方法,JDBC中获取连接的DriverManager等。

1.3 建造者模式(Builder Pattern)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

java
1public class Computer {
2 // 必要参数
3 private final String CPU;
4 private final String RAM;
5 // 可选参数
6 private final String storage;
7 private final String graphicsCard;
8
9 private Computer(Builder builder) {
10 this.CPU = builder.CPU;
11 this.RAM = builder.RAM;
12 this.storage = builder.storage;
13 this.graphicsCard = builder.graphicsCard;
14 }
15
16 public static class Builder {
17 // 必要参数
18 private final String CPU;
19 private final String RAM;
20 // 可选参数
21 private String storage;
22 private String graphicsCard;
23
24 public Builder(String CPU, String RAM) {
25 this.CPU = CPU;
26 this.RAM = RAM;
27 }
28
29 public Builder storage(String storage) {
30 this.storage = storage;
31 return this;
32 }
33
34 public Builder graphicsCard(String graphicsCard) {
35 this.graphicsCard = graphicsCard;
36 return this;
37 }
38
39 public Computer build() {
40 return new Computer(this);
41 }
42 }
43}
44
45// 使用建造者模式
46Computer computer = new Computer.Builder("Intel i7", "16GB")
47 .storage("1TB SSD")
48 .graphicsCard("NVIDIA RTX 3080")
49 .build();

应用:Java中的StringBuilder、StringBuffer、Lombok的@Builder注解、Spring中的BeanDefinitionBuilder等。

1.4 原型模式(Prototype Pattern)

通过复制现有实例来创建新的实例,而不是通过构造函数。

java
1// Cloneable接口实现原型模式
2public class Shape implements Cloneable {
3 private String type;
4
5 public String getType() {
6 return type;
7 }
8
9 public void setType(String type) {
10 this.type = type;
11 }
12
13 @Override
14 public Shape clone() {
15 try {
16 return (Shape) super.clone();
17 } catch (CloneNotSupportedException e) {
18 return null;
19 }
20 }
21}
22
23// 使用原型模式
24Shape circle = new Shape();
25circle.setType("Circle");
26Shape clonedCircle = circle.clone();

应用:Java中的Object.clone()方法、Spring中的Bean作用域prototype等。

2. 结构型模式(Structural Patterns)

结构型模式关注如何组合类和对象以形成更大的结构,同时保持结构的灵活性和高效性。

2.1 适配器模式(Adapter Pattern)

将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的类能一起工作。

java
1// 目标接口
2public interface MediaPlayer {
3 void play(String audioType, String fileName);
4}
5
6// 被适配的接口
7public interface AdvancedMediaPlayer {
8 void playVlc(String fileName);
9 void playMp4(String fileName);
10}
11
12// 被适配的类
13public class VlcPlayer implements AdvancedMediaPlayer {
14 @Override
15 public void playVlc(String fileName) {
16 System.out.println("Playing vlc file: " + fileName);
17 }
18
19 @Override
20 public void playMp4(String fileName) {
21 // 什么都不做
22 }
23}
24
25// 适配器
26public class MediaAdapter implements MediaPlayer {
27 private AdvancedMediaPlayer advancedMusicPlayer;
28
29 public MediaAdapter(String audioType) {
30 if (audioType.equalsIgnoreCase("vlc")) {
31 advancedMusicPlayer = new VlcPlayer();
32 } else if (audioType.equalsIgnoreCase("mp4")) {
33 advancedMusicPlayer = new Mp4Player();
34 }
35 }
36
37 @Override
38 public void play(String audioType, String fileName) {
39 if (audioType.equalsIgnoreCase("vlc")) {
40 advancedMusicPlayer.playVlc(fileName);
41 } else if (audioType.equalsIgnoreCase("mp4")) {
42 advancedMusicPlayer.playMp4(fileName);
43 }
44 }
45}

应用:Java I/O中的InputStreamReader和OutputStreamWriter、JDBC中的适配器等。

2.2 装饰器模式(Decorator Pattern)

动态地给一个对象添加一些额外的职责,比生成子类更加灵活。

java
1// 组件接口
2public interface Component {
3 void operation();
4}
5
6// 具体组件
7public class ConcreteComponent implements Component {
8 @Override
9 public void operation() {
10 System.out.println("ConcreteComponent operation");
11 }
12}
13
14// 装饰器
15public abstract class Decorator implements Component {
16 protected Component component;
17
18 public Decorator(Component component) {
19 this.component = component;
20 }
21
22 @Override
23 public void operation() {
24 component.operation();
25 }
26}
27
28// 具体装饰器
29public class ConcreteDecoratorA extends Decorator {
30 public ConcreteDecoratorA(Component component) {
31 super(component);
32 }
33
34 @Override
35 public void operation() {
36 super.operation();
37 addedBehavior();
38 }
39
40 private void addedBehavior() {
41 System.out.println("Added behavior from ConcreteDecoratorA");
42 }
43}

应用:Java I/O中的装饰器模式(如InputStream、BufferedInputStream等)、Swing中的BorderDecorator等。

2.3 代理模式(Proxy Pattern)

为其他对象提供一种代理以控制对这个对象的访问。

java
1// 接口
2public interface Image {
3 void display();
4}
5
6// 实际类
7public class RealImage implements Image {
8 private String fileName;
9
10 public RealImage(String fileName) {
11 this.fileName = fileName;
12 loadFromDisk();
13 }
14
15 private void loadFromDisk() {
16 System.out.println("Loading " + fileName);
17 }
18
19 @Override
20 public void display() {
21 System.out.println("Displaying " + fileName);
22 }
23}
24
25// 代理类
26public class ProxyImage implements Image {
27 private RealImage realImage;
28 private String fileName;
29
30 public ProxyImage(String fileName) {
31 this.fileName = fileName;
32 }
33
34 @Override
35 public void display() {
36 if (realImage == null) {
37 realImage = new RealImage(fileName);
38 }
39 realImage.display();
40 }
41}
42
43// 使用代理
44Image image = new ProxyImage("test.jpg");
45// 第一次调用display()时才加载图片
46image.display();

应用:Spring中的AOP和依赖注入、Hibernate中的延迟加载、Java RMI等。

2.4 组合模式(Composite Pattern)

将对象组合成树形结构以表示"部分-整体"的层次结构,使客户端可以统一对待单个对象和组合对象。

java
1// 组件接口
2public interface Component {
3 void operation();
4}
5
6// 叶子组件
7public class Leaf implements Component {
8 private String name;
9
10 public Leaf(String name) {
11 this.name = name;
12 }
13
14 @Override
15 public void operation() {
16 System.out.println("Leaf " + name + " operation");
17 }
18}
19
20// 复合组件
21public class Composite implements Component {
22 private List<Component> children = new ArrayList<>();
23 private String name;
24
25 public Composite(String name) {
26 this.name = name;
27 }
28
29 public void add(Component component) {
30 children.add(component);
31 }
32
33 public void remove(Component component) {
34 children.remove(component);
35 }
36
37 @Override
38 public void operation() {
39 System.out.println("Composite " + name + " operation");
40 for (Component child : children) {
41 child.operation();
42 }
43 }
44}

应用:Swing中的组件层次结构、文件系统的表示等。

2.5 外观模式(Facade Pattern)

为子系统中的一组接口提供一个一致的界面,使子系统更容易使用。

java
1// 子系统类
2public class CPU {
3 public void freeze() { System.out.println("CPU freeze"); }
4 public void jump(long position) { System.out.println("CPU jump to position " + position); }
5 public void execute() { System.out.println("CPU execute"); }
6}
7
8public class Memory {
9 public void load(long position, byte[] data) {
10 System.out.println("Memory load from position " + position);
11 }
12}
13
14public class HardDrive {
15 public byte[] read(long lba, int size) {
16 System.out.println("HardDrive read sector " + lba);
17 return new byte[size];
18 }
19}
20
21// 外观类
22public class ComputerFacade {
23 private CPU cpu;
24 private Memory memory;
25 private HardDrive hardDrive;
26
27 public ComputerFacade() {
28 this.cpu = new CPU();
29 this.memory = new Memory();
30 this.hardDrive = new HardDrive();
31 }
32
33 public void start() {
34 cpu.freeze();
35 memory.load(0, hardDrive.read(0, 1024));
36 cpu.jump(0);
37 cpu.execute();
38 }
39}
40
41// 使用外观模式
42ComputerFacade computer = new ComputerFacade();
43computer.start();

应用:SLF4J对不同日志框架的抽象、Spring中的JdbcTemplate等。

2.6 桥接模式(Bridge Pattern)

将抽象部分与其实现部分分离,使它们都可以独立地变化。

java
1// 实现接口
2public interface DrawAPI {
3 void drawCircle(int radius, int x, int y);
4}
5
6// 具体实现
7public class RedCircle implements DrawAPI {
8 @Override
9 public void drawCircle(int radius, int x, int y) {
10 System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]");
11 }
12}
13
14public class GreenCircle implements DrawAPI {
15 @Override
16 public void drawCircle(int radius, int x, int y) {
17 System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]");
18 }
19}
20
21// 抽象类
22public abstract class Shape {
23 protected DrawAPI drawAPI;
24
25 protected Shape(DrawAPI drawAPI) {
26 this.drawAPI = drawAPI;
27 }
28
29 public abstract void draw();
30}
31
32// 扩展抽象类
33public class Circle extends Shape {
34 private int x, y, radius;
35
36 public Circle(int x, int y, int radius, DrawAPI drawAPI) {
37 super(drawAPI);
38 this.x = x;
39 this.y = y;
40 this.radius = radius;
41 }
42
43 @Override
44 public void draw() {
45 drawAPI.drawCircle(radius, x, y);
46 }
47}
48
49// 使用桥接模式
50Shape redCircle = new Circle(100, 100, 10, new RedCircle());
51Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());
52redCircle.draw();
53greenCircle.draw();

应用:JDBC中的Driver和DriverManager、AWT中的平台相关实现等。

3. 行为型模式(Behavioral Patterns)

行为型模式关注对象间的通信,描述如何分配职责和算法。

3.1 策略模式(Strategy Pattern)

定义一系列算法,封装每个算法,并使它们可以互换。策略模式让算法独立于使用它的客户端。

java
1// 策略接口
2public interface PaymentStrategy {
3 void pay(int amount);
4}
5
6// 具体策略
7public class CreditCardStrategy implements PaymentStrategy {
8 private String cardNumber;
9 private String cvv;
10 private String expiryDate;
11
12 public CreditCardStrategy(String cardNumber, String cvv, String expiryDate) {
13 this.cardNumber = cardNumber;
14 this.cvv = cvv;
15 this.expiryDate = expiryDate;
16 }
17
18 @Override
19 public void pay(int amount) {
20 System.out.println(amount + " paid with credit card");
21 }
22}
23
24public class PayPalStrategy implements PaymentStrategy {
25 private String email;
26 private String password;
27
28 public PayPalStrategy(String email, String password) {
29 this.email = email;
30 this.password = password;
31 }
32
33 @Override
34 public void pay(int amount) {
35 System.out.println(amount + " paid using PayPal");
36 }
37}
38
39// 上下文
40public class ShoppingCart {
41 private PaymentStrategy paymentStrategy;
42
43 public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
44 this.paymentStrategy = paymentStrategy;
45 }
46
47 public void checkout(int amount) {
48 paymentStrategy.pay(amount);
49 }
50}
51
52// 使用策略模式
53ShoppingCart cart = new ShoppingCart();
54cart.setPaymentStrategy(new CreditCardStrategy("1234-5678-9012-3456", "123", "12/24"));
55cart.checkout(100);
56
57cart.setPaymentStrategy(new PayPalStrategy("example@example.com", "password"));
58cart.checkout(200);

应用:Java中的Comparator接口、Spring中的Resource接口等。

3.2 观察者模式(Observer Pattern)

定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动收到通知并更新。

java
1// 观察者接口
2public interface Observer {
3 void update(String message);
4}
5
6// 具体观察者
7public class User implements Observer {
8 private String name;
9
10 public User(String name) {
11 this.name = name;
12 }
13
14 @Override
15 public void update(String message) {
16 System.out.println(name + " received: " + message);
17 }
18}
19
20// 被观察者/主题
21public class Topic {
22 private List<Observer> observers = new ArrayList<>();
23 private String message;
24
25 public void register(Observer observer) {
26 observers.add(observer);
27 }
28
29 public void unregister(Observer observer) {
30 observers.remove(observer);
31 }
32
33 public void postMessage(String message) {
34 this.message = message;
35 notifyObservers();
36 }
37
38 public void notifyObservers() {
39 for (Observer observer : observers) {
40 observer.update(message);
41 }
42 }
43}
44
45// 使用观察者模式
46Topic topic = new Topic();
47Observer user1 = new User("User 1");
48Observer user2 = new User("User 2");
49
50topic.register(user1);
51topic.register(user2);
52topic.postMessage("Hello World!");

应用:Java中的事件监听机制、JavaBeans中的属性变更通知机制、Spring中的ApplicationEvent等。

3.3 命令模式(Command Pattern)

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

java
1// 命令接口
2public interface Command {
3 void execute();
4}
5
6// 具体命令
7public class LightOnCommand implements Command {
8 private Light light;
9
10 public LightOnCommand(Light light) {
11 this.light = light;
12 }
13
14 @Override
15 public void execute() {
16 light.turnOn();
17 }
18}
19
20public class LightOffCommand implements Command {
21 private Light light;
22
23 public LightOffCommand(Light light) {
24 this.light = light;
25 }
26
27 @Override
28 public void execute() {
29 light.turnOff();
30 }
31}
32
33// 接收者
34public class Light {
35 public void turnOn() {
36 System.out.println("Light is on");
37 }
38
39 public void turnOff() {
40 System.out.println("Light is off");
41 }
42}
43
44// 调用者
45public class RemoteControl {
46 private Command command;
47
48 public void setCommand(Command command) {
49 this.command = command;
50 }
51
52 public void pressButton() {
53 command.execute();
54 }
55}
56
57// 使用命令模式
58Light light = new Light();
59Command lightOn = new LightOnCommand(light);
60Command lightOff = new LightOffCommand(light);
61
62RemoteControl remote = new RemoteControl();
63remote.setCommand(lightOn);
64remote.pressButton();
65
66remote.setCommand(lightOff);
67remote.pressButton();

应用:Java中的Runnable接口、Swing中的Action接口、Spring中的JdbcTemplate中的回调等。

3.4 模板方法模式(Template Method Pattern)

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法的结构即可重定义算法的某些特定步骤。

java
1// 抽象类定义模板方法
2public abstract class Game {
3 // 模板方法,定义算法骨架
4 public final void play() {
5 initialize();
6 startPlay();
7 endPlay();
8 }
9
10 // 子类必须实现的抽象方法
11 protected abstract void initialize();
12 protected abstract void startPlay();
13 protected abstract void endPlay();
14}
15
16// 具体子类
17public class Cricket extends Game {
18 @Override
19 protected void initialize() {
20 System.out.println("Cricket Game Initialized!");
21 }
22
23 @Override
24 protected void startPlay() {
25 System.out.println("Cricket Game Started!");
26 }
27
28 @Override
29 protected void endPlay() {
30 System.out.println("Cricket Game Finished!");
31 }
32}
33
34public class Football extends Game {
35 @Override
36 protected void initialize() {
37 System.out.println("Football Game Initialized!");
38 }
39
40 @Override
41 protected void startPlay() {
42 System.out.println("Football Game Started!");
43 }
44
45 @Override
46 protected void endPlay() {
47 System.out.println("Football Game Finished!");
48 }
49}
50
51// 使用模板方法模式
52Game cricket = new Cricket();
53cricket.play();
54
55Game football = new Football();
56football.play();

应用:Java中的各种抽象类、Spring中的JdbcTemplate、Hibernate中的各种回调等。

3.5 迭代器模式(Iterator Pattern)

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

java
1// 迭代器接口
2public interface Iterator<T> {
3 boolean hasNext();
4 T next();
5}
6
7// 容器接口
8public interface Container<T> {
9 Iterator<T> getIterator();
10}
11
12// 具体容器
13public class NameRepository implements Container<String> {
14 private String[] names = {"Robert", "John", "Julie", "Lora"};
15
16 @Override
17 public Iterator<String> getIterator() {
18 return new NameIterator();
19 }
20
21 private class NameIterator implements Iterator<String> {
22 private int index;
23
24 @Override
25 public boolean hasNext() {
26 return index < names.length;
27 }
28
29 @Override
30 public String next() {
31 if (hasNext()) {
32 return names[index++];
33 }
34 return null;
35 }
36 }
37}
38
39// 使用迭代器模式
40Container<String> namesContainer = new NameRepository();
41Iterator<String> iterator = namesContainer.getIterator();
42
43while (iterator.hasNext()) {
44 String name = iterator.next();
45 System.out.println("Name: " + name);
46}

应用:Java集合框架中的Iterator接口等。

4. 其他重要设计模式

4.1 状态模式(State Pattern)

允许对象在内部状态改变时改变它的行为,使对象看起来好像修改了它的类。

java
1// 状态接口
2public interface State {
3 void doAction(Context context);
4}
5
6// 具体状态
7public class StartState implements State {
8 @Override
9 public void doAction(Context context) {
10 System.out.println("Player is in start state");
11 context.setState(this);
12 }
13
14 @Override
15 public String toString() {
16 return "Start State";
17 }
18}
19
20public class StopState implements State {
21 @Override
22 public void doAction(Context context) {
23 System.out.println("Player is in stop state");
24 context.setState(this);
25 }
26
27 @Override
28 public String toString() {
29 return "Stop State";
30 }
31}
32
33// 上下文
34public class Context {
35 private State state;
36
37 public Context() {
38 state = null;
39 }
40
41 public void setState(State state) {
42 this.state = state;
43 }
44
45 public State getState() {
46 return state;
47 }
48}
49
50// 使用状态模式
51Context context = new Context();
52
53StartState startState = new StartState();
54startState.doAction(context);
55System.out.println(context.getState().toString());
56
57StopState stopState = new StopState();
58stopState.doAction(context);
59System.out.println(context.getState().toString());

应用:Java线程的状态变化、工作流引擎中的状态迁移等。

4.2 责任链模式(Chain of Responsibility Pattern)

为请求创建一个接收者对象的链,每个接收者都包含对另一个接收者的引用。沿着这条链传递请求,直到有一个对象处理它。

java
1// 处理者抽象类
2public abstract class AbstractLogger {
3 public static final int INFO = 1;
4 public static final int DEBUG = 2;
5 public static final int ERROR = 3;
6
7 protected int level;
8 protected AbstractLogger nextLogger;
9
10 public void setNextLogger(AbstractLogger nextLogger) {
11 this.nextLogger = nextLogger;
12 }
13
14 public void logMessage(int level, String message) {
15 if (this.level <= level) {
16 write(message);
17 }
18 if (nextLogger != null) {
19 nextLogger.logMessage(level, message);
20 }
21 }
22
23 protected abstract void write(String message);
24}
25
26// 具体处理者
27public class ConsoleLogger extends AbstractLogger {
28 public ConsoleLogger(int level) {
29 this.level = level;
30 }
31
32 @Override
33 protected void write(String message) {
34 System.out.println("Standard Console::Logger: " + message);
35 }
36}
37
38public class FileLogger extends AbstractLogger {
39 public FileLogger(int level) {
40 this.level = level;
41 }
42
43 @Override
44 protected void write(String message) {
45 System.out.println("File::Logger: " + message);
46 }
47}
48
49// 使用责任链模式
50AbstractLogger loggerChain = getChainOfLoggers();
51loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
52loggerChain.logMessage(AbstractLogger.DEBUG, "This is a debug level information.");
53loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information.");
54
55// 创建责任链
56private static AbstractLogger getChainOfLoggers() {
57 AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
58 AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
59 AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
60
61 errorLogger.setNextLogger(fileLogger);
62 fileLogger.setNextLogger(consoleLogger);
63
64 return errorLogger;
65}

应用:Java中的异常处理机制、Servlet中的过滤器链、Spring中的拦截器链等。

5. 设计模式在Java实际应用中的最佳实践

5.1 选择合适的设计模式

  • 分析问题的具体需求和约束条件
  • 考虑模式的优缺点和适用场景
  • 选择最简单、最适合的设计模式
  • 避免过度设计和滥用设计模式

5.2 设计模式的组合使用

实际应用中,通常需要组合多种设计模式来解决复杂问题。例如:

  • Spring框架同时使用了工厂模式、单例模式、代理模式、观察者模式等
  • Java集合框架中使用了迭代器模式、工厂模式、装饰器模式等
  • Java I/O中使用了装饰器模式、工厂模式、适配器模式等

5.3 设计模式的实际案例

Spring框架中的设计模式应用

  • IOC容器:工厂模式、单例模式
  • AOP:代理模式、装饰器模式
  • MVC:复合模式(前端控制器、视图助手、模板视图)
  • 事务管理:模板方法模式、代理模式

Java集合框架中的设计模式应用

  • ArrayList/LinkedList等:迭代器模式
  • Collections.unmodifiableXxx():装饰器模式
  • Collections.synchronizedXxx():装饰器模式
  • Arrays.asList():适配器模式

Java并发包中的设计模式应用

  • Executor框架:命令模式
  • Future/Callable:命令模式
  • CompletableFuture:观察者模式
  • ThreadPoolExecutor:工厂方法模式、模板方法模式

43. Java反射机制的原理和应用有哪些?

**反射(Reflection)**是Java的一个强大特性,允许程序在运行时检查和操作类、接口、方法和字段。通过反射,程序可以获取到在编译时无法获取的类型信息,并能够实例化对象、调用方法、访问和修改字段,即使它们是私有的。

1. 反射的基本概念

反射允许程序:

  • 在运行时检查类、接口、字段和方法
  • 在运行时创建类的实例
  • 在运行时获取和设置对象的字段值
  • 在运行时调用对象的方法
  • 在运行时检查注解

反射的核心API位于java.lang.reflect包中,主要包括:

  • Class<?>: 类的元信息
  • Field: 表示类的成员变量
  • Method: 表示类的方法
  • Constructor: 表示类的构造方法
  • Modifier: 提供访问修饰符信息的工具类
  • Array: 提供动态创建和访问数组的静态方法
  • Parameter: Java 8后添加,提供方法参数信息

2. 获取Class对象的方式

反射的起点是获取一个类的Class对象,有以下几种方式:

java
1// 1. 通过类名.class
2Class<?> clazz1 = String.class;
3
4// 2. 通过对象的getClass()方法
5String str = "Hello";
6Class<?> clazz2 = str.getClass();
7
8// 3. 通过Class.forName()方法
9try {
10 Class<?> clazz3 = Class.forName("java.lang.String");
11} catch (ClassNotFoundException e) {
12 e.printStackTrace();
13}
14
15// 4. 通过ClassLoader
16ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
17try {
18 Class<?> clazz4 = classLoader.loadClass("java.lang.String");
19} catch (ClassNotFoundException e) {
20 e.printStackTrace();
21}
22
23// 5. 基本类型的Class对象
24Class<?> intClass = int.class;
25
26// 6. 基本类型包装类的TYPE字段
27Class<?> integerClass = Integer.TYPE; // 等同于int.class

3. 反射的主要应用场景

3.1 获取类的结构信息

java
1// 获取类的基本信息
2Class<?> clazz = MyClass.class;
3String className = clazz.getName(); // 获取完全限定名
4String simpleName = clazz.getSimpleName(); // 获取简单类名
5Package pkg = clazz.getPackage(); // 获取包信息
6Class<?> superClass = clazz.getSuperclass(); // 获取父类
7Class<?>[] interfaces = clazz.getInterfaces(); // 获取实现的接口
8int modifiers = clazz.getModifiers(); // 获取修饰符
9boolean isPublic = Modifier.isPublic(modifiers); // 检查修饰符
10
11// 获取字段信息
12Field[] fields = clazz.getDeclaredFields(); // 获取所有声明的字段
13Field[] publicFields = clazz.getFields(); // 获取所有公共字段(包括继承的)
14
15for (Field field : fields) {
16 String fieldName = field.getName(); // 字段名
17 Class<?> fieldType = field.getType(); // 字段类型
18 int fieldModifiers = field.getModifiers(); // 字段修饰符
19}
20
21// 获取方法信息
22Method[] methods = clazz.getDeclaredMethods(); // 获取所有声明的方法
23Method[] publicMethods = clazz.getMethods(); // 获取所有公共方法(包括继承的)
24
25for (Method method : methods) {
26 String methodName = method.getName(); // 方法名
27 Class<?> returnType = method.getReturnType(); // 返回类型
28 Class<?>[] paramTypes = method.getParameterTypes(); // 参数类型
29 Class<?>[] exceptionTypes = method.getExceptionTypes(); // 异常类型
30}
31
32// 获取构造方法信息
33Constructor<?>[] constructors = clazz.getDeclaredConstructors(); // 获取所有声明的构造方法
34Constructor<?>[] publicConstructors = clazz.getConstructors(); // 获取所有公共构造方法

3.2 创建类的实例

java
1// 使用默认构造方法创建实例
2Class<?> clazz = MyClass.class;
3try {
4 Object obj = clazz.newInstance(); // 已过时,Java 9开始不推荐使用
5} catch (InstantiationException | IllegalAccessException e) {
6 e.printStackTrace();
7}
8
9// 使用Constructor创建实例(推荐)
10try {
11 // 获取默认构造方法
12 Constructor<?> constructor = clazz.getDeclaredConstructor();
13 Object obj = constructor.newInstance();
14
15 // 获取带参数的构造方法
16 Constructor<?> paramConstructor = clazz.getDeclaredConstructor(String.class, int.class);
17 Object paramObj = paramConstructor.newInstance("name", 25);
18} catch (Exception e) {
19 e.printStackTrace();
20}

3.3 访问和修改字段

java
1// 访问公共字段
2Class<?> clazz = MyClass.class;
3Object obj = clazz.getDeclaredConstructor().newInstance();
4try {
5 Field field = clazz.getField("publicField"); // 只能获取公共字段
6 Object value = field.get(obj); // 获取字段值
7 field.set(obj, "new value"); // 设置字段值
8} catch (Exception e) {
9 e.printStackTrace();
10}
11
12// 访问私有字段
13try {
14 Field privateField = clazz.getDeclaredField("privateField");
15 privateField.setAccessible(true); // 设置访问性,绕过访问检查
16 Object value = privateField.get(obj); // 获取私有字段值
17 privateField.set(obj, "new private value"); // 修改私有字段值
18} catch (Exception e) {
19 e.printStackTrace();
20}
21
22// 访问静态字段
23try {
24 Field staticField = clazz.getDeclaredField("CONSTANT");
25 staticField.setAccessible(true);
26 Object value = staticField.get(null); // 静态字段不需要实例,传null
27} catch (Exception e) {
28 e.printStackTrace();
29}

3.4 调用方法

java
1// 调用公共方法
2Class<?> clazz = MyClass.class;
3Object obj = clazz.getDeclaredConstructor().newInstance();
4try {
5 // 无参方法
6 Method method = clazz.getMethod("publicMethod");
7 Object result = method.invoke(obj);
8
9 // 有参方法
10 Method paramMethod = clazz.getMethod("publicParamMethod", String.class, int.class);
11 Object paramResult = paramMethod.invoke(obj, "hello", 42);
12} catch (Exception e) {
13 e.printStackTrace();
14}
15
16// 调用私有方法
17try {
18 Method privateMethod = clazz.getDeclaredMethod("privateMethod");
19 privateMethod.setAccessible(true); // 绕过访问检查
20 Object result = privateMethod.invoke(obj);
21} catch (Exception e) {
22 e.printStackTrace();
23}
24
25// 调用静态方法
26try {
27 Method staticMethod = clazz.getDeclaredMethod("staticMethod");
28 staticMethod.setAccessible(true);
29 Object result = staticMethod.invoke(null); // 静态方法不需要实例,传null
30} catch (Exception e) {
31 e.printStackTrace();
32}

3.5 访问注解

java
1// 获取类上的注解
2Class<?> clazz = MyClass.class;
3if (clazz.isAnnotationPresent(MyAnnotation.class)) {
4 MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
5 String value = annotation.value();
6}
7
8// 获取方法上的注解
9Method method = clazz.getDeclaredMethod("myMethod");
10if (method.isAnnotationPresent(MyMethodAnnotation.class)) {
11 MyMethodAnnotation annotation = method.getAnnotation(MyMethodAnnotation.class);
12}
13
14// 获取字段上的注解
15Field field = clazz.getDeclaredField("myField");
16if (field.isAnnotationPresent(MyFieldAnnotation.class)) {
17 MyFieldAnnotation annotation = field.getAnnotation(MyFieldAnnotation.class);
18}
19
20// 获取参数上的注解(Java 8+)
21Method method2 = clazz.getDeclaredMethod("myMethod", String.class);
22Parameter[] parameters = method2.getParameters();
23for (Parameter parameter : parameters) {
24 if (parameter.isAnnotationPresent(MyParamAnnotation.class)) {
25 MyParamAnnotation annotation = parameter.getAnnotation(MyParamAnnotation.class);
26 String value = annotation.value();
27 }
28}
29
30// 获取所有注解
31Annotation[] annotations = clazz.getAnnotations(); // 包括继承的注解
32Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations(); // 仅直接声明的注解

3.6 数组操作

java
1// 创建数组
2int[] intArray = (int[]) Array.newInstance(int.class, 5);
3
4// 设置数组元素
5Array.setInt(intArray, 0, 10);
6Array.setInt(intArray, 1, 20);
7
8// 获取数组元素
9int value = Array.getInt(intArray, 0);
10
11// 创建多维数组
12int[][] matrix = (int[][]) Array.newInstance(int.class, 3, 3);
13
14// 获取数组类型信息
15Class<?> clazz = intArray.getClass();
16boolean isArray = clazz.isArray();
17Class<?> componentType = clazz.getComponentType();

4. 反射的实际应用

4.1 框架和库中的应用

反射在许多Java框架和库中得到广泛应用:

  • Spring: 依赖注入、AOP等核心功能

    • 通过反射创建bean
    • 自动装配依赖
    • 使用注解配置
  • Hibernate/JPA: ORM映射

    • 将实体类映射到数据库表
    • 通过注解或XML配置访问字段
  • Jackson/Gson: JSON序列化和反序列化

    • 将JSON字符串转换为Java对象
    • 将Java对象转换为JSON字符串
  • JUnit: 测试框架

    • 使用注解标记测试方法
    • 自动运行测试案例
  • Android: 视图绑定等

    • 利用注解绑定视图和控件

4.2 插件和扩展系统

许多应用程序使用反射来实现插件或扩展系统:

java
1// 动态加载和实例化插件
2public Plugin loadPlugin(String className) throws Exception {
3 Class<?> pluginClass = Class.forName(className);
4 if (Plugin.class.isAssignableFrom(pluginClass)) {
5 return (Plugin) pluginClass.getDeclaredConstructor().newInstance();
6 }
7 throw new IllegalArgumentException("Not a valid plugin: " + className);
8}
9
10// 扫描包中的所有插件
11public List<Plugin> scanPlugins(String packageName) {
12 // 获取包下所有类
13 List<Class<?>> classes = getClassesInPackage(packageName);
14 List<Plugin> plugins = new ArrayList<>();
15
16 for (Class<?> clazz : classes) {
17 // 检查是否是插件类型且有@PluginAnnotation
18 if (Plugin.class.isAssignableFrom(clazz) &&
19 clazz.isAnnotationPresent(PluginAnnotation.class)) {
20 try {
21 Plugin plugin = (Plugin) clazz.getDeclaredConstructor().newInstance();
22 plugins.add(plugin);
23 } catch (Exception e) {
24 // 处理异常
25 }
26 }
27 }
28
29 return plugins;
30}

4.3 动态代理

反射可以用来创建动态代理,这是很多框架(如Spring AOP)的基础:

java
1// 定义接口
2interface UserService {
3 void addUser(String username);
4 String getUser(int id);
5}
6
7// 定义InvocationHandler
8class LoggingHandler implements InvocationHandler {
9 private final Object target;
10
11 public LoggingHandler(Object target) {
12 this.target = target;
13 }
14
15 @Override
16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
17 System.out.println("Before method: " + method.getName());
18 try {
19 Object result = method.invoke(target, args);
20 System.out.println("After method: " + method.getName());
21 return result;
22 } catch (Exception e) {
23 System.out.println("Exception in method: " + method.getName());
24 throw e;
25 }
26 }
27}
28
29// 创建代理
30UserService userService = new UserServiceImpl();
31UserService proxy = (UserService) Proxy.newProxyInstance(
32 UserService.class.getClassLoader(),
33 new Class<?>[] { UserService.class },
34 new LoggingHandler(userService)
35);
36
37// 调用代理方法
38proxy.addUser("user1"); // 会触发日志记录

4.4 依赖注入容器

简单的依赖注入容器实现:

java
1public class DIContainer {
2 private Map<Class<?>, Object> instances = new HashMap<>();
3
4 public <T> void register(Class<T> clazz) throws Exception {
5 Constructor<T> constructor = clazz.getDeclaredConstructor();
6 T instance = constructor.newInstance();
7
8 // 查找@Inject注解的字段
9 for (Field field : clazz.getDeclaredFields()) {
10 if (field.isAnnotationPresent(Inject.class)) {
11 Class<?> fieldType = field.getType();
12 field.setAccessible(true);
13
14 // 递归创建依赖
15 if (!instances.containsKey(fieldType)) {
16 register(fieldType);
17 }
18
19 field.set(instance, instances.get(fieldType));
20 }
21 }
22
23 instances.put(clazz, instance);
24 }
25
26 @SuppressWarnings("unchecked")
27 public <T> T get(Class<T> clazz) {
28 return (T) instances.get(clazz);
29 }
30}
31
32// 使用依赖注入容器
33DIContainer container = new DIContainer();
34container.register(UserService.class);
35UserService service = container.get(UserService.class);

5. 反射的性能考虑

反射虽然强大,但也有性能开销:

  1. 访问检查:特别是访问私有成员时需要绕过访问检查
  2. 类型安全检查:运行时进行而非编译时
  3. 缺少编译时优化:无法进行内联等优化

性能优化策略:

  1. 缓存反射对象:重复使用Class、Method、Field等对象
  2. 减少反射调用:仅在必要时使用反射
  3. 使用setAccessible(true):减少访问检查的开销
  4. 使用方法句柄(MethodHandle):Java 7引入的更高效替代方案
java
1// 缓存反射对象示例
2public class ReflectionCache {
3 private static final Map<String, Method> methodCache = new ConcurrentHashMap<>();
4
5 public static Method getMethod(Class clazz, String methodName, Class... paramTypes)
6 throws NoSuchMethodException {
7 String key = clazz.getName() + "#" + methodName + Arrays.toString(paramTypes);
8 return methodCache.computeIfAbsent(key, k -> {
9 try {
10 return clazz.getDeclaredMethod(methodName, paramTypes);
11 } catch (NoSuchMethodException e) {
12 throw new RuntimeException(e);
13 }
14 });
15 }
16}

6. 反射的安全性和限制

反射打破了Java的封装性,可能绕过访问控制。为了安全考虑:

  1. SecurityManager:可以控制反射操作
  2. 模块系统(Java 9+):可以限制反射访问
  3. 运行时权限:可以配置安全策略
java
1// 设置安全管理器
2System.setSecurityManager(new SecurityManager());
3
4// 安全策略示例(在策略文件中)
5// permission java.lang.reflect.ReflectPermission "suppressAccessChecks";

注意事项

  • 反射不能访问无法通过正常代码访问的成员(如父类的private字段)
  • 某些JVM优化可能不适用于使用反射的代码
  • 对于模块化应用(Java 9+),需要考虑opensexports声明

7. 方法句柄(MethodHandle)

Java 7引入的方法句柄提供了一种比反射更高效的动态方法调用机制:

java
1import java.lang.invoke.MethodHandle;
2import java.lang.invoke.MethodHandles;
3import java.lang.invoke.MethodType;
4
5public class MethodHandleExample {
6 public static void main(String[] args) throws Throwable {
7 // 获取MethodHandles.Lookup对象
8 MethodHandles.Lookup lookup = MethodHandles.lookup();
9
10 // 查找方法
11 MethodType methodType = MethodType.methodType(String.class, int.class);
12 MethodHandle handle = lookup.findVirtual(String.class, "substring", methodType);
13
14 // 调用方法
15 String result = (String) handle.invokeExact("Hello, World", 0);
16
17 // 绑定接收者
18 MethodHandle boundHandle = handle.bindTo("Hello, World");
19 String result2 = (String) boundHandle.invokeExact(0);
20 }
21}

与反射的比较

  • 方法句柄性能通常更好
  • 方法句柄类型安全性更强
  • 反射更灵活,可以获取更多元信息
  • 反射更容易使用和理解

8. 反射与注解处理

反射常用于运行时处理注解:

java
1// 自定义注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.METHOD)
4public @interface Transactional {
5 boolean readOnly() default false;
6}
7
8// 使用反射处理注解
9public class TransactionAspect {
10 public Object invoke(Object target, Method method, Object[] args) throws Throwable {
11 if (method.isAnnotationPresent(Transactional.class)) {
12 Transactional tx = method.getAnnotation(Transactional.class);
13 boolean readOnly = tx.readOnly();
14
15 // 开始事务
16 startTransaction(readOnly);
17
18 try {
19 Object result = method.invoke(target, args);
20 commitTransaction();
21 return result;
22 } catch (Exception e) {
23 rollbackTransaction();
24 throw e;
25 }
26 } else {
27 // 无事务注解,直接调用
28 return method.invoke(target, args);
29 }
30 }
31
32 // 事务操作实现...
33}

9. 反射的最佳实践

  1. 谨慎使用:仅在必要时使用反射,不要仅为方便而使用
  2. 异常处理:反射API会抛出多种异常,需要妥善处理
  3. 性能优化:缓存反射对象,避免重复获取
  4. 访问控制:使用setAccessible(true)时考虑安全影响
  5. 类型安全:尽量使用泛型提高类型安全性
  6. 可读性:使用有意义的命名和注释
  7. 测试覆盖:反射代码需要更多测试覆盖
  8. 考虑替代方案:如工厂模式、依赖注入框架等

10. 反射的实际案例分析

案例:自定义ORM框架

实现一个简单的ORM映射,通过反射将Java对象映射到数据库表:

java
1// 表映射注解
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.TYPE)
4public @interface Table {
5 String name();
6}
7
8// 列映射注解
9@Retention(RetentionPolicy.RUNTIME)
10@Target(ElementType.FIELD)
11public @interface Column {
12 String name();
13 boolean primary() default false;
14}
15
16// 实体类示例
17@Table(name = "users")
18public class User {
19 @Column(name = "id", primary = true)
20 private Long id;
21
22 @Column(name = "username")
23 private String username;
24
25 @Column(name = "email")
26 private String email;
27
28 // 构造方法、getter和setter...
29}
30
31// ORM工具类
32public class OrmUtil {
33 // 生成插入SQL
34 public static String generateInsertSQL(Object entity) {
35 Class<?> clazz = entity.getClass();
36 if (!clazz.isAnnotationPresent(Table.class)) {
37 throw new IllegalArgumentException("Class not annotated with @Table");
38 }
39
40 Table table = clazz.getAnnotation(Table.class);
41 String tableName = table.name();
42
43 List<String> columns = new ArrayList<>();
44 List<String> values = new ArrayList<>();
45
46 try {
47 for (Field field : clazz.getDeclaredFields()) {
48 if (field.isAnnotationPresent(Column.class)) {
49 field.setAccessible(true);
50 Column column = field.getAnnotation(Column.class);
51
52 // 跳过自增主键
53 if (column.primary() && field.get(entity) == null) {
54 continue;
55 }
56
57 columns.add(column.name());
58
59 Object value = field.get(entity);
60 if (value instanceof String) {
61 values.add("'" + value + "'");
62 } else {
63 values.add(String.valueOf(value));
64 }
65 }
66 }
67 } catch (Exception e) {
68 throw new RuntimeException("Error generating SQL", e);
69 }
70
71 return String.format("INSERT INTO %s (%s) VALUES (%s)",
72 tableName,
73 String.join(", ", columns),
74 String.join(", ", values));
75 }
76
77 // 生成查询SQL并映射结果
78 public static <T> T findById(Class<T> clazz, Object id, Connection conn) {
79 if (!clazz.isAnnotationPresent(Table.class)) {
80 throw new IllegalArgumentException("Class not annotated with @Table");
81 }
82
83 Table table = clazz.getAnnotation(Table.class);
84 String tableName = table.name();
85 String primaryKeyColumn = "";
86
87 // 查找主键列
88 for (Field field : clazz.getDeclaredFields()) {
89 if (field.isAnnotationPresent(Column.class)) {
90 Column column = field.getAnnotation(Column.class);
91 if (column.primary()) {
92 primaryKeyColumn = column.name();
93 break;
94 }
95 }
96 }
97
98 if (primaryKeyColumn.isEmpty()) {
99 throw new IllegalArgumentException("No primary key found");
100 }
101
102 String sql = String.format("SELECT * FROM %s WHERE %s = ?",
103 tableName, primaryKeyColumn);
104
105 try (PreparedStatement stmt = conn.prepareStatement(sql)) {
106 stmt.setObject(1, id);
107
108 try (ResultSet rs = stmt.executeQuery()) {
109 if (rs.next()) {
110 return mapResultSetToObject(rs, clazz);
111 }
112 }
113 } catch (Exception e) {
114 throw new RuntimeException("Error executing query", e);
115 }
116
117 return null;
118 }
119
120 // 将结果集映射到对象
121 private static <T> T mapResultSetToObject(ResultSet rs, Class<T> clazz)
122 throws Exception {
123 T instance = clazz.getDeclaredConstructor().newInstance();
124
125 for (Field field : clazz.getDeclaredFields()) {
126 if (field.isAnnotationPresent(Column.class)) {
127 field.setAccessible(true);
128 Column column = field.getAnnotation(Column.class);
129 String columnName = column.name();
130
131 Object value = rs.getObject(columnName);
132
133 if (value != null) {
134 // 处理类型转换
135 if (field.getType() == int.class || field.getType() == Integer.class) {
136 field.set(instance, rs.getInt(columnName));
137 } else if (field.getType() == long.class || field.getType() == Long.class) {
138 field.set(instance, rs.getLong(columnName));
139 } else if (field.getType() == double.class || field.getType() == Double.class) {
140 field.set(instance, rs.getDouble(columnName));
141 } else if (field.getType() == boolean.class || field.getType() == Boolean.class) {
142 field.set(instance, rs.getBoolean(columnName));
143 } else if (field.getType() == Date.class) {
144 field.set(instance, rs.getDate(columnName));
145 } else {
146 field.set(instance, value);
147 }
148 }
149 }
150 }
151
152 return instance;
153 }
154}
forum

评论区 / Comments