1. 引言

在Java开发中,理解可变对象(Mutable Objects)和不可变对象(Immutable Objects)的区别至关重要。这两个概念直接影响代码的行为和设计模式。

本文将深入探讨两种对象的定义、示例、优缺点及适用场景,帮助你在实际开发中做出明智选择。

2. 不可变对象

不可变对象一旦创建,其状态就无法被修改。 从实例化到生命周期结束,其内部属性始终保持不变。

2.1. String类

Java中String的不可变性带来三大核心优势:

  • 线程安全
  • 增强安全性
  • 通过String池机制优化内存使用
@Test
public void givenImmutableString_whenConcatString_thenNotSameAndCorrectValues() {
    String originalString = "Hello";
    String modifiedString = originalString.concat(" World");

    assertNotSame(originalString, modifiedString);

    assertEquals("Hello", originalString);
    assertEquals("Hello World", modifiedString);
}

这个测试用例中,concat()方法会创建新字符串,原始字符串保持不变——这是不可变对象的典型特征

2.2. Integer类

Integer类同样是不可变的。对Integer执行运算时,会创建新实例存储结果。

@Test
public void givenImmutableInteger_whenAddInteger_thenNotSameAndCorrectValue() {
    Integer immutableInt = 42;
    Integer modifiedInt = immutableInt + 8;

    assertNotSame(immutableInt, modifiedInt);

    assertEquals(42, (int) immutableInt);
    assertEquals(50, (int) modifiedInt);
}

+运算符操作Integer时,原始对象不会被修改,而是生成新对象——踩坑提醒:别指望能直接修改Integer的值!

2.3. 不可变对象的优势

使用不可变对象能带来显著收益:

线程安全:无需同步机制即可在多线程间安全共享
可预测性:状态恒定使代码行为更易推理
缓存友好:状态不变使对象可被高效缓存和复用

简单粗暴地说:不可变对象天生自带"防篡改"属性,特别适合高并发场景。

3. 创建不可变对象

ImmutablePerson类为例,展示不可变对象的实现要点:

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

关键设计原则:

  • 类声明为final(防止继承)
  • 字段均为private final
  • 不提供setter方法

尝试修改对象状态会直接报错:

ImmutablePerson person = new ImmutablePerson("John", 30);
person.setName("Jane"); // 编译错误!

这种设计确保对象从创建到销毁都保持恒定状态,是构建可靠系统的基石。

4. 可变对象

可变对象在创建后允许修改其内部状态,这种灵活性在特定场景下很有价值。

4.1. StringBuilder类

StringBuilder是典型的可变字符序列:

@Test
public void givenMutableString_whenAppendElement_thenCorrectValue() {
    StringBuilder mutableString = new StringBuilder("Hello");
    mutableString.append(" World");

    assertEquals("Hello World", mutableString.toString());
}

append()方法直接修改对象内部状态——与String的不可变性形成鲜明对比

4.2. ArrayList类

ArrayList作为动态数组,其可变性体现在:

@Test
public void givenMutableList_whenAddElement_thenCorrectSize() {
    List<String> mutableList = new ArrayList<>();
    mutableList.add("Java");

    assertEquals(1, mutableList.size());
}

add()方法改变集合状态,展示可变对象的动态特性。

4.3. 使用注意事项

可变对象虽灵活,但需警惕以下问题:

⚠️ 线程安全风险:多线程环境下需额外同步机制
⚠️ 代码复杂度:状态变化增加理解难度
⚠️ 状态管理挑战:需严格跟踪和控制修改

踩坑经验:在并发场景中滥用可变对象,往往会导致诡异的并发问题

5. 可变 vs 不可变对象对比

对比维度 可变对象 不可变对象
可修改性 创建后可修改 创建后恒定不变
线程安全 需同步机制保障 天然线程安全
可预测性 状态变化增加理解难度 行为可预测,调试简单
性能影响 同步开销可能影响性能 通常提升性能(如缓存优化)

5.1. 如何选择

决策时需权衡以下因素:

  1. 需求优先级

    • 需要频繁修改状态 → 选择可变对象
    • 需要稳定性和安全性 → 选择不可变对象
  2. 并发场景

    • 多线程共享数据 → 优先不可变对象(避免同步复杂性)
  3. 性能考量

    • 数据极少变化 → 不可变对象(缓存收益大)
    • 频繁修改数据 → 可变对象(避免创建开销)

经验法则:默认优先考虑不可变对象,除非有明确理由需要可变性

6. 总结

在Java中合理使用可变与不可变对象,是构建高质量系统的关键:

  • 不可变对象:提供线程安全、可预测性和性能优化,适合共享数据场景
  • 可变对象:提供灵活性,适合需要频繁修改状态的场景

最终选择应基于具体需求:评估并发需求、性能瓶颈和代码复杂度,在灵活性与稳定性间找到平衡点。记住——没有银弹,只有最适合当前场景的方案


原始标题:Mutable vs. Immutable Objects in Java | Baeldung