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

总结

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

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

相关推荐
用户925807911481 分钟前
画图理解mysql日志机制
java·后端
javahongxi4 分钟前
Spring Cloud Trace 链路实现
java·spring boot·spring cloud
海梨花5 分钟前
腾讯面试高频算法题
java·算法·面试
于先生吖6 分钟前
Java消息队列优化抢单逻辑,同城搬家拉货多场景业务数据库架构设计
java·开发语言·数据库架构
半个烧饼不加肉7 分钟前
JS 底层探究--执行上下文
开发语言·前端·javascript
小谢小哥8 分钟前
68-持续集成详解
java·后端·架构
用户925807911488 分钟前
redission原理
java·后端
小旭95279 分钟前
Spring Cloud 集成分布式日志 ELK+Swagger 接口文档实战
java·分布式·后端·elk·spring cloud
屋外雨大,惊蛰出没11 分钟前
spring boot+mybatis开发基础复习
java·spring boot·后端