1. 概述

在本教程中,我们将学习是什么使对象不可变,如何在 Java 中实现不可变性,以及这样做有什么好处。

2. 什么是不可变对象?

不可变对象是其内部状态在完全创建后保持不变的对象。

这意味着不可变对象的公共 API 保证了它在其整个生命周期中的行为方式相同。

如果我们看一下类 String,我们可以看到,即使它的 API 似乎通过其replace方法为我们提供了可变行为,原始字符串也不会改变:

String name = "testjack";

String newName = name.replace("jack", "----");

assertEquals("testjack", name);

assertEquals("test----", newName);Copy

API 为我们提供了只读方法,它绝不应包含更改对象内部状态的方法。

3. Java中的final 关键字

在尝试在 Java 中实现不变性之前,我们应该讨论final关键字。

在 Java 中,变量默认是可变的,这意味着我们可以更改它们保存的值。

通过在声明变量时使用final关键字,Java 编译器不允许我们更改该变量的值。相反,它将报告编译时错误:

final String name = "baeldung";

name = "bael...";Copy

请注意,final仅禁止我们更改变量持有的引用,它不会保护我们使用其公共 API 更改它引用的对象的内部状态:

final List strings = new ArrayList<>();

assertEquals(0, strings.size());

strings.add("testjack");

assertEquals(0, strings.size());Copy

第二个assertEquals将失败,因为将元素添加到列表中会更改其大小,因此,它不是一个不可变的对象。

4. Java中的不可变性

现在我们知道了如何避免更改变量的内容,我们可以使用它来构建不可变对象的 API。

构建不可变对象的 API 需要我们保证无论我们如何使用其 API 都不会更改其内部状态。

朝着正确方向前进的一步是在声明其属性时使用final:

class Money {

private final double amount;

private final Currency currency;

// ...

}Copy

请注意,Java保证了金额的值不会改变,所有基元类型变量都是如此。

但是,在我们的示例中,我们只能保证货币不会更改,因此我们必须依靠货币API 来保护自身免受更改。

大多数时候,我们需要对象的属性来保存自定义值,而初始化不可变对象的内部状态的地方是它的构造函数:

class Money {

// ...

public Money(double amount, Currency currency) {

this.amount = amount;

this.currency = currency;

}

public Currency getCurrency() {

return currency;

}

public double getAmount() {

return amount;

}

}Copy

正如我们之前所说,为了满足不可变 API 的要求,我们的Money类只有只读方法。

使用反射 API,我们可以打破不可变性并更改不可变对象。但是,反射违反了不可变对象的公共 API,通常,我们应该避免这样做。

5. 好处

由于不可变对象的内部状态在时间上保持不变,因此我们可以在多个线程之间安全地共享它。

我们也可以自由使用它,引用它的对象都不会注意到任何差异,我们可以说不可变的对象没有副作用。

6. 结论

不可变对象不会及时更改其内部状态,它们是线程安全的,并且没有副作用。由于这些属性,不可变对象在处理多线程环境时也特别有用。

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。