Java不可变对象(Immutable Objects)

本文深入探讨了不可变对象的概念,解释了其在Java平台类库中的应用,特别是通过String类实例来展示不可变对象的特点。文章还讨论了不可变对象的优势,包括线程安全性、无需复制和复用的可能性,并提供了设计不可变类的最佳实践,如声明为final、保护性拷贝等。通过示例代码展示了如何正确设计不可变类,以及在实际应用中遇到的挑战和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是不可变对象?
不可变对象(Immutable Objects)即对象一旦被创建,它的状态(即对象属性值)不能被修改。
不可变对象的类即为不可变类(Immutable Class)。Java平台类库中包含的不可变类,如String、基本类型的包装类、BigInteger和BigDecimal。
String类的实例就是典型的不可变对象,每次对对象的修改都将创建一个新的String对象,而原来的对象保持不变。
关于String的不可变更多 参见这里

为什么需要不可变对象?好处?
优点:
1.线程安全且不存在同步问题,可以不用被synchronize就在并发环境中共享,也不需要使用额外的锁机制在线程中共享,不需要担心数据会被其它线程修改;
2.当用作类的属性时不需要保护性拷贝;
3.可以将它们缓存起来重复使用
缺点:
创建对象的开销,如经常对字符串进行修改,产生的对象驻留在内存引起很大的内存开销

怎么设计不可变类(Immutable Class)?

  • 将类声明为final, 不能被继承,或使用静态工厂并声明构造器为private,以避免子类改变了父类的immutable特性
  • 所有的属性都应该是final
  • 不要提供任何可以修改对象状态的方法 - 不仅仅是set方法, 还有任何其它可以改变状态的方法
  • 如果类有任何可变对象属性, 那么当它们在类和类的调用者间传递的时候必须被保护性拷贝,返回给客户端对象的拷贝

有时候你要实现的immutable类中可能包含mutable的类,比如java.util.Date,尽管你将其设置成了final的,但是它的值还是可以被修改的,为了避免这个问题,我们建议返回给用户该对象的一个拷贝。

import java.util.Date;
/**
 * Planet是一个不可变类,因为当它构造完成之后没有办法改变它的状态
 */
public final class Planet {
    /**
     * 声明为final的基本类型数据总是不可变的
     */
    private final double fMass;
    /**
     * 不可变的对象属性 (String对象不可变)
     */
    private final String fName;
    /**
     * 可变的对象属性. 在这种情况下, 这个可变属性只能被这个类改变。
     * (在其它情况下, 允许在原生类外部改变一个属性是很有意义的;
     * 这种情况就是当属性作为其它地方创建的一个对象引用)
     */
    private final Date fDateOfDiscovery;
    public Planet(double aMass, String aName, Date aDateOfDiscovery) {
        fMass = aMass;
        fName = aName;
        //创建aDateOfDiscovery的一个私有拷贝
        //这是保持fDateOfDiscovery属性为private的唯一方式, 并且保护这个
        //类不受调用者对于原始aDateOfDiscovery对象所做任何改变的影响
        fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
    }
    /**
     * 返回一个基本类型值.
     *
     * 调用者可以随意改变返回值,但是不会影响类内部。
     */
    public double getMass() {
        return fMass;
    }
    /**
     * 返回一个不可变对象
     *
     * 调用者得到内部属性的一个直接引用. 由于String是不可变的所以没什么影响
     */
    public String getName() {
        return fName;
    }
// /**
// * 返回一个可变对象 - 不是一个好的方式.
// *
// * 调用者得到内部属性的一个直接引用. 这通常很危险,因为Date对象既可以
// * 被这个类改变也可以被它的调用者改变.即,类不再对fDate拥有绝对的控制。
// */
// public Date getDateOfDiscovery() {
// return fDateOfDiscovery;
// }
    /**
     * 返回一个可变对象 - 好的方式.
     *
     * 返回属性的一个保护性拷贝.调用者可以任意改变返回的Date对象,但是不会
     * 影响类的内部.为什么? 因为它们没有fDate的一个引用. 更准确的说, 它们
     * 使用的是和fDate有着相同数据的另一个Date对象
     */
    public Date getDateOfDiscovery() {
        return new Date(fDateOfDiscovery.getTime());
    }
    /**
     * 测试方法
     * @param args
     */
    public static void main(String[] args) {
        Planet planet = new Planet(1.0D, "earth", new Date());
        Date date = planet.getDateOfDiscovery();
        date.setTime(111111111L);
        System.out.println("the value of fDateOfDiscovery of internal class : " + planet.fDateOfDiscovery.getTime());
        System.out.println("the value of date after change its value : " + date.getTime());
    }

运行结果如下:

 the value of fDateOfDiscovery of internal class : 1393943752205
 the value of date after change its value : 111111111

参考:
http://my.oschina.net/jackieyeah/blog/205198?fromerr=yJ78hGIV
http://my.oschina.net/jasonultimate/blog/166810?fromerr=J2n1Za00

<think>嗯,用户之前问了为什么数组可以改变,但int和Integer不行,现在他们接着问还有哪些是不可变的。这说明他们对不可变对象的概念感兴趣,可能是在学习Java或者类似的编程语言,想要更深入理解不可变性。 我需要先回忆一下Java中的不可变对象有哪些常见的例子。比如String,还有所有的包装类比如Integer、Double、Boolean这些。另外,BigInteger和BigDecimal也是不可变的。可能用户还希望了解其他语言中的情况,但根据之前的回答,用户的问题集中在Java上,所以应该以Java为主。 还要注意用户可能对不可变对象的设计原因和好处感兴趣,比如线程安全、缓存、哈希表键等。但用户直接问的是“还有哪些是不可变的”,所以重点应该是列举例子,同时可能需要简要说明为什么它们是不可变的,以及和之前提到的数组和基本类型的区别。 另外,用户可能分不清不可变对象和不可变变量,比如final修饰的变量是不可变变量,但对象本身是否可变要看类型。需要澄清这一点。比如,虽然一个final的List引用不能指向其他对象,但如果List本身是可变的,内容还是可以修改。而不可变对象是指实例创建后状态不能改变,比如String。 可能还需要提到一些集合框架中的不可变集合,比如通过Collections.unmodifiableList创建的,或者Java 9引入的不可变集合工厂方法。但严格来说,这些是通过包装实现的不可变视图,而真正的不可变集合可能在创建后不能修改,比如Guava库中的ImmutableList。 另外,用户可能想知道如何自己创建不可变类,比如通过final类,私有字段,没有setter方法,深度拷贝等。但这个问题可能属于另一个话题,用户现在只是问有哪些现有的不可变类型。 需要确保回答结构清晰,先总述不可变对象的概念,然后分点列举Java中的例子,每个例子简要说明其不可变性的体现,最后总结不可变的好处和应用场景。同时要避免技术术语过多,用简单的中文解释,符合用户的学习需求。 还要检查是否有遗漏的重要不可变类型,比如枚举类型,虽然枚举实例是单例的,但它们的属性是否可变取决于定义。通常枚举也被设计为不可变的。此外,一些第三方库中的类,比如Joda-Time的DateTime,也是不可变的,但可能用户更关注标准库中的内容。 最后需要确认回答是否覆盖了用户可能的疑问,比如为什么这些类被设计为不可变,以及在实际编程中如何利用它们的特性。可能需要用例子说明,比如String的连接操作实际生成新对象,而不会改变原有对象。</think>--- ### **不可变对象Immutable Objects)的核心定义** 不可变对象指**实例创建后,其内部状态(属性值)无法被修改**。在Java中,不可变对象具有以下特点: 1. 所有字段为`final`且私有 2. 不提供修改内部状态的方法(如`setter`) 3. 若包含可变对象的引用,需通过防御性拷贝(Defensive Copy)对外暴露 --- ### **Java中常见的不可变类型** #### **1. 基本数据类型的包装类** - **`Integer`、`Double`、`Boolean`等** ```java Integer a = 10; a = 20; // 实际是创建新对象,原对象`10`不可变 ``` - **特点**:所有数值操作(如`a++`)都会生成新对象。 #### **2. `String`类** - **最经典的不可变对象** ```java String s = "hello"; s = s + " world"; // 创建新对象"hello world",原对象"hello"不变 ``` - **底层实现**:`private final char[] value`(Java 8及之前)或`byte[]`(Java 9+)。 #### **3. `BigInteger`与`BigDecimal`** - **大整数/高精度浮点数的不可变性** ```java BigDecimal num = new BigDecimal("10.5"); num = num.add(new BigDecimal("2.3")); // 返回新对象,原对象仍为10.5 ``` #### **4. 枚举(Enum)类型** - **枚举实例在初始化后不可修改** ```java enum Status { RUNNING, STOPPED } // 所有枚举实例的状态固定,无法动态改变 ``` #### **5. 不可变集合(Java 9+)** - **通过工厂方法创建的集合** ```java List<String> list = List.of("A", "B"); // 不可变列表 list.add("C"); // 抛出UnsupportedOperationException ``` - **包含**:`List.of()`、`Set.of()`、`Map.of()`等。 #### **6. 自定义不可变类** - **通过严格设计实现不可变性** ```java public final class ImmutablePerson { private final String name; private final int age; public ImmutablePerson(String name, int age) { this.name = name; this.age = age; } // 仅提供getter,无setter } ``` --- ### **不可变对象 vs 不可变引用** | **概念** | **说明** | |--------------------|--------------------------------------------------------------------------| | **不可变对象** | 对象本身的状态不可修改(如`String`) | | **不可变引用** | 变量引用的指向不可修改(如`final List<String> list = new ArrayList<>()`) | | **关键区别** | 不可变引用仍可能指向可变对象(如`list.add("A")`是允许的) | --- ### **为什么需要不可变对象?** 1. **线程安全**:无需同步锁,天然支持多线程共享。 2. **缓存优化**:可复用对象(如`Integer`缓存-128~127)。 3. **哈希稳定性**:哈希值在生命周期内不变,适合作为`HashMap`的键。 4. **函数式编程**:避免副作用,符合纯函数设计理念。 --- ### **不可变对象的局限性** 1. **频繁修改时性能低**:每次修改需创建新对象(如字符串拼接)。 2. **深拷贝成本高**:若包含复杂结构(如嵌套集合),防御性拷贝可能影响性能。 --- ### **如何判断一个类是否不可变?** 1. 检查所有字段是否为`private final`。 2. 确认
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值