首页 / JAVA / Java 中的不可变数据结构
Java 中的不可变数据结构
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java 中的不可变数据结构,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5004字,纯文字阅读大概需要8分钟。
内容图文
最近,在我主导的几场代码面试中,经常出现不可变数据结构(Immutable Data Structure)相关内容。关于这个主题我个人并不过分教条,不变性通常体现在数据结构中,"除非必要"否则不会要求代码一定具备不变性。然而,我发现大家对不变性(Immutability)这个概念似乎有一些误解。开发者通常认为加上 `final`,或者在 Kotlin、Scala 中加上 `val` 就足以实现不可变对象。这篇文章会深入讨论不可变引用和不可变数据结构。
1. 不可变数据结构的优点
不可变数据结构有下列显著优点:
没有无效状态(Invalid State)
线程安全
代码易于理解
易于测试
可用作值类型
译注:在计算机编程中包含两种类型,值类型 value type 与引用类型 reference type。值类型表示实际值,引用类型表示对其他值或对象的引用。
2. 没有无效状态
不可变对象只能通过构造函数初始化,并且通过参数限制了输入的有效性,从而确保对象不会包含无效值。例如下面这段代码示例:
```java
Address address = new Address();
address.setCity("Sydney");
// 由于没有设置 country,address 现在处于无效状态.
Address address = new Address("Sydney", "Australia");
// Address 对象有效并且不提供 setter 方法,因此 address 对象会一直保持有效.
```
3. 线程安全
由于对象值不可修改,在线程间共享时不会产生竞态条件或者数据突变问题。
4. 代码易于理解
在上面的示例代码中,使用构造函数比初始化方法更易于理解。构造函数会强制检查输入参数,而 setter 或初始化方法不会在编译时进行检查。
5. 易于测试
使用初始化方法,必须测试调用顺序对对象的影响。而使用构造函数,对象的值要么有效要么无效,无需进行排列组合测试。代码执行结果的可靠性更强,出现 `NullPointerExceptions` 的机率更小。下面是一个传递对象过程中改变了对象状态的示例:
```java
public boolean isOverseas(Address address) {
?if(address.getCountry().equals("Australia") == false) {
? ?address.setOverseas(true); // address 的值发生了改变!
? ?return true;
?} else {
? ?return false;
?}
}
```
上面的代码是一种错误示范,在返回 `boolean` 结果的同时改变了对象状态。这样的代码可读性和可测性都很差。一种更好的方法是从 `Address` 类中移除 setter 方法,为 `country` 属性提供 `boolean` 类型的测试方法;更进一步,可以把 `address.isOverseas()` 的逻辑移到 `Address` 类中。需要设置状态时,拷贝原来的对象而非修改输入对象的值。
6. 可作为值类型使用
如何做到使用 `Money` 对象表示10美金,使用的时候一直是10美金?比如这段代码,`public Money(final BigInteger amount, final Currency currency)` 确保了一旦声明10美金后接下来不会改变。这样对象的值可以安全地作为值类型使用。
7. final 并不能让对象变成不可变对象
文章开头提到过,我经常遇到开发者不能完全理解 `final` 引用和不可变对象的区别。最常见误区是,只要在变量前加上 `final` 就会成为不可变数据结构。不幸的是,实际并没有这么简单。接下来会为大家消除这个误解:
在变量前加 `final` 不会产生不可变对象。
换句话说,下面这段代码生成的对象是可变对象:
```java
final Person person = new Person("John");
```
尽管 `person` 是一个 final 字段不能重新赋值,但 `Person` 类可能提供了 setter 方法或者其他修改方法,比如像下面这个方法:
```java
person.setName("Cindy");
```
无论是否加 `final` 修饰符,轻易就可以修改对象。不仅如此,`Person` 类可能还提供了许多修改 address 属性的类似方法,调用它们可以向对象添加地址,同样会修改 `person` 对象。
```java
person.getAddresses().add(new Address("Sydney"));
```
`final` 引用并没能阻止修改对象。
现在我们已经澄清了这个误解,接下来讨论如何让类具有不可变的特性。在设计时需要考虑以下事项:
不要把内部状态暴露出来
不要在内部修改状态
确保子类不会破坏上面的行为
按照上面这些建议,让我们重新设计 `Person` 类:
```java
public final class Person { ?// final 类, 不支持重载
?private final String name; ? ? // 加 final 修饰, 支持多线程
?private final List<Address> addresses;
?public Person(String name, List<Address> addresses) {
? ?this.name = name;
? ?this.addresses = List.copyOf(addresses); ? // 拷贝列表, 避免从外面修改对象 (Java 10+).
? ? ? ? ? ? ? ?// 也可以使用 Collections.unmodifiableList(new ArrayList<>(addresses));
?}
?public String getName() {
? ?return this.name; ? // String 是不可变对象, 可以暴露
?}
?public List<Address> getAddresses() {
? ?return addresses; // Address list 可以修改
?}
}
public final class Address { ? ?// final 类, 不支持重载
? ?private final String city; ? ? ? // 只使用不可变类
? ?private final String country;
? ?public Address(String city, String country) {
? ? ? ?this.city = city;
? ? ? ?this.country = country;
? ?}
? ?public String getCity() {
? ? ? ?return city;
? ?}
? ?public String getCountry() {
? ? ? ?return country;
? ?}
}
```
现在,代码变成下面这样:
```java
import java.util.List;
final Person person = new Person("John", List.of(new Address(“Sydney”, "Australia"));
```
更新后的 `Person` 和 `Address` 让上面的代码成为不可变代码。不仅如此,`final` 引用让 `person` 变量无法再次赋值。
更新:正如评论中[指出的][1],上面的代码还是可以修改的,因为并没有在构造函数中执行列表拷贝。如果不在构造函数中调用 `new ArrayList()` 还可以像下面这样做:
```java
final List<Address> addresses = new ArrayList<>();
addresses.add(new Address("Sydney", "Australia"));
final Person person = new Person("John", addressList);
addresses.clear();
```
[1]:https://www.reddit.com/r/java/comments/azryu6/final_vs_immutable_data_structures_in_java/?st=jt74o32w&sh=40d418d3
由于不在构造函数中执行 `copy`,上面的代码无法修改 `Person` 类中拷贝后的 address list,这样代码就安全了。感谢指正!
内容总结
以上是互联网集市为您收集整理的Java 中的不可变数据结构全部内容,希望文章能够帮你解决Java 中的不可变数据结构所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。