Java 中的不可变对象
1. 概述
在本教程中,我们将学习是什么使对象不可变,如何在 Java 中实现不可变性,以及这样做有什么好处。
2. 什么是不可变对象?
不可变对象是其内部状态在完全创建后保持不变的对象。
这意味着不可变对象的公共 API 保证了它在其整个生命周期中的行为方式相同。
如果我们看一下类 String,我们可以看到,即使它的 API 似乎通过其replace方法为我们提供了可变行为,原始字符串也不会改变:
代码语言:javascript代码运行次数:0运行复制String name = "testjack";
String newName = name.replace("jack", "----");
assertEquals("testjack", name);
assertEquals("test----", newName);Copy
API 为我们提供了只读方法,它绝不应包含更改对象内部状态的方法。
3. Java中的final 关键字
在尝试在 Java 中实现不变性之前,我们应该讨论final关键字。
在 Java 中,变量默认是可变的,这意味着我们可以更改它们保存的值。
通过在声明变量时使用final关键字,Java 编译器不允许我们更改该变量的值。相反,它将报告编译时错误:
代码语言:javascript代码运行次数:0运行复制final String name = "baeldung";
name = "bael...";Copy
请注意,final仅禁止我们更改变量持有的引用,它不会保护我们使用其公共 API 更改它引用的对象的内部状态:
代码语言:javascript代码运行次数:0运行复制final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("testjack");
assertEquals(0, strings.size());Copy
第二个assertEquals将失败,因为将元素添加到列表中会更改其大小,因此,它不是一个不可变的对象。
4. Java中的不可变性
现在我们知道了如何避免更改变量的内容,我们可以使用它来构建不可变对象的 API。
构建不可变对象的 API 需要我们保证无论我们如何使用其 API 都不会更改其内部状态。
朝着正确方向前进的一步是在声明其属性时使用final:
代码语言:javascript代码运行次数:0运行复制class Money {
private final double amount;
private final Currency currency;
// ...
}Copy
请注意,Java保证了金额的值不会改变,所有基元类型变量都是如此。
但是,在我们的示例中,我们只能保证货币不会更改,因此我们必须依靠货币API 来保护自身免受更改。
大多数时候,我们需要对象的属性来保存自定义值,而初始化不可变对象的内部状态的地方是它的构造函数:
代码语言:javascript代码运行次数:0运行复制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. 结论
不可变对象不会及时更改其内部状态,它们是线程安全的,并且没有副作用。由于这些属性,不可变对象在处理多线程环境时也特别有用。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-02-22,如有侵权请联系 cloudcommunity@tencent 删除反射教程java变量对象