java中的不可变类(Immutable)

文章目录

前言

在多线程并发编程中,为了保证对象状态的原子性、可见性和有序性,开发者通常会使用 synchronized 等同步机制。

然而,过多的同步不仅会增加代码复杂度,还会影响性能。

如果我们将类设计为不可变的,那么对象的状态自始至终都不会改变。

这样一来,每次需要修改状态时都会产生一个全新的对象供不同线程使用,从而从根本上消除了并发安全问题。

一、什么是不可变类

一个类的对象在通过构造方法创建后,如果其状态绝对不会再被改变,那么这个类就是不可变(Immutable)类。

不可变对象具有以下核心特征:

  • 所有成员变量的赋值操作仅在构造方法中完成。
  • 不对外提供任何 setter 方法,彻底切断外部类修改其内部状态的途径。

二、常见的不可变类

提到不可变类,Java中最经典的就是 String 类,此外还包括 IntegerLong 等基本数据类型的包装类。String 类被设计为不可变的,主要基于以下三个方面的考量:

  1. 常量池的需要

    字符串常量池是Java堆内存中的一个特殊存储区域。创建 String 对象时,如果该字符串在常量池中不存在,则新建一个;如果已存在,则直接复用已有的引用地址。这种机制能够大幅减少JVM的内存开销,提升运行效率。

  2. hashCode 缓存

    由于字符串状态不可变,在其创建之初 hashCode 就可以被计算并缓存起来。这使得 String 非常适合作为哈希表(如 HashMap)的键,多次调用只需返回同一个哈希值,极大地提高了检索效率。

  3. 线程安全

    不可变对象天然具备线程安全性。在多线程环境下共享 String 对象时,不会出现不可预期的结果,因此也无需进行额外的同步处理。即使我们调用 String 类的 trim()substring()toLowerCase() 等方法,原字符串对象依然完好无损,方法会返回一个经过处理的全新对象。

三、代码实践

理解不可变类很容易,但手写一个严谨的自定义不可变类需要严格遵守以下四个条件:

  1. 确保类是 final 的:防止该类被其他类继承,从而避免子类重写方法破坏其不可变性。
  2. 确保所有的成员变量是 final 的:强制字段只能在构造方法中初始化,后续绝无法被修改。
  3. 不提供任何 setter 方法:关闭外部修改对象状态的入口。
  4. 如果包含可变对象,必须返回副本 :这是最容易忽略的一点。如果不可变类内部持有其他可变类的对象(例如一个普通的 Book 类),在对外提供该对象的 getter 方法时,必须返回该对象的防御性副本,绝不能直接暴露其底层的引用地址。否则,外部代码可以通过该引用地址轻易篡改原对象的数据。

代码实现示例:

Java 复制代码
// 一个普通的可变类
public class Book {
    private String name;
    private int price;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getPrice() { return price; }
    public void setPrice(int price) { this.price = price; }
}

// 自定义的不可变类
public final class Writer {
    private final String name;
    private final int age;
    private final Book book;

    public Writer(String name, int age, Book book) {
        this.name = name;
        this.age = age;
        this.book = book;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    // 核心点:必须返回内部可变对象的克隆副本,防止引用地址外泄
    public Book getBook() {
        Book clone = new Book();
        clone.setName(this.book.getName());
        clone.setPrice(this.book.getPrice());
        return clone;
    }
}

在上面的测试用例中,如果不采用防御性拷贝返回 clone 对象,外部对返回的 Book 进行 setPrice 操作时,就会破坏 Writer 类的不可变性。

总结

不可变类通过牺牲极少量的内存(修改时需创建新对象),换取了多线程环境下的绝对安全。

在实际开发中,尤其是构建多线程高并发系统时,这种"无锁化"的设计思路极为重要

相关推荐
Ulyanov1 小时前
《PySide6 GUI开发指南:QML核心与实践》 第一篇:GUI新纪元——QML与PySide6生态系统全景
开发语言·python·qt·qml·雷达电子对抗
Rust研习社2 小时前
从入门到实践:Rust 异步编程完全指南
开发语言·后端·rust
yaoxin5211232 小时前
389. Java IO API - 获取文件名
java·开发语言·python
Wang15303 小时前
Java排序
java
逸风尊者3 小时前
XGBoost模型工程使用
java·后端·算法
一嘴一个橘子3 小时前
MP 自定义业务方法 (二)
java
lhbian3 小时前
AI编程革命:Codex让脚本开发提速10倍
开发语言·汇编·jvm·c#
jiayong233 小时前
第 36 课:任务详情抽屉快捷改状态
开发语言·前端·javascript·vue.js·学习
低客的黑调3 小时前
MyBatis-Plus-从 CRUD 到高级特性
java·servlet·tomcat