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 类的不可变性。

总结

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

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

相关推荐
白鲸开源1 天前
Apache SeaTunnel Zeta Engine 的 Basic Auth 是怎么工作的?
java·vue.js·github
白鲸开源1 天前
一文读懂DolphinScheduler插件机制:如何轻松扩展任务类型与数据源
java·架构·github
用户298698530141 天前
Java 实现 Word 文档文本查找与高亮标注
java·后端
宇宙之一粟1 天前
乐企版式文件生成平台
java·后端·python
plainGeekDev1 天前
MVC 写法 → MVVM
android·java·kotlin
SL_staff1 天前
3周搭完MES系统:JVS低代码+JVS-IoT物联网的实战记录
java·前端·低代码
MacroZheng1 天前
斩获20w star!Claude Code最强插件,AI编程必备!
java·人工智能·后端
唐青枫1 天前
Java Spring WebFlux 实战指南:用 Mono、Flux 和 WebClient 写响应式接口
java·spring
小bo波2 天前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
SamDeepThinking2 天前
高并发场景下,CompletableFuture与ForkJoinPool该如何取舍?
java·后端·面试