您好,登录后才能下订单哦!
本篇文章为大家展示了Java中不可变数据结构是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
不可变数据结构的好处
不可变数据结构具有显着优势,例如:
没有无效的状态
线程安全
易于理解的代码
更容易测试代码
可用于值类型
没有无效的状态
当一个对象是不可变的时,很难让对象处于无效状态。该对象只能通过其构造函数实例化,这将强制对象的有效性。这样,可以强制执行有效状态所需的参数。一个例子:
Address address = new Address(); address.setCity("Sydney"); // address is in invalid state now, since the country hasn't been set. Address address = new Address("Sydney", "Australia"); // Address is valid and doesn't have setters, so the address object is always valid.
线程安全
由于无法更改对象,因此可以在线程之间共享它,而不会出现竞争条件或数据突变问题。
易于理解的代码
与无效状态的代码示例类似,使用构造函数通常比初始化方法更容易。这是因为构造函数强制执行必需的参数,而setter或initializer方法在编译时不会强制执行。
更易于测试的代码
由于对象更具可预测性,因此不必测试初始化方法的所有排列,即在调用类的构造函数时,该对象有效或无效。使用这些类的代码的其他部分变得更可预测,具有更少的NullPointerException机会。有时,当传递对象时,有些方法可能会改变对象的状态。例如:
public boolean isOverseas(Address address) { if(address.getCountry().equals("Australia") == false) { address.setOverseas(true); // address has now been mutated! return true; } else { return false; } }
一般来说,上面的代码是不好的做法。它返回一个布尔值,并可能改变对象的状态。这使得代码更难理解和测试。更好的解决方案是从Address 类中删除setter ,并通过测试国家名称返回一个布尔值。更好的方法是将此逻辑移动到 Address 类本身(address.isOverseas())。当确实需要设置状态时,在不改变输入的情况下制作原始对象的副本。
可用于值类型
想象一下金额,比如10美元。10美元将永远是10美元。在代码中,这可能看起来像 public Money(final BigInteger amount, final Currency currency)。正如您在此代码中看到的那样,不可能将10美元的值更改为除此之外的任何值,因此,上述内容可以安全地用于值类型。
最终引用不要使对象不可变
如前所述,我经常遇到的问题之一是这些开发人员中的很大一部分并不完全理解最终引用和不可变对象之间的区别。似乎这些开发人员的共同理解是,变量成为最终的那一刻,数据结构变得不可变。不幸的是,这并不是那么简单,我想一劳永逸地把这种误解带出世界:
A final reference does not make your objects immutable!
换句话说,下面的代码并没有使对象不变:
final Person person = new Person("John");
为什么不?好吧,虽然person是最后一个字段而且无法重新分配,但是 Person类可能有一个setter方法或其他mutator方法,可以执行如下操作:
person.setName("Cindy");
无论最终修饰符如何,这都是一件非常容易的事情。或者, Person类可能会公开这样的地址列表。访问此列表允许您向其添加地址,因此,如下所示改变 person对象:
person.getAddresses().add(new Address("Sydney"));
好了,既然我们已经解决了这个问题,那么让我们深入了解一下我们如何使类不可变。在设计我们的类时,我们需要记住几件事:
不要以可变的方式暴露内部状态
要在内部改变状态
确保子类不会覆盖上述行为
根据以下准则,让我们设计一个更好的Person class 版本 。
public final class Person {// final class, can't be overridden by subclasses private final String name; // final for safe publication in multithreaded applications private final List<Address> addresses; public Person(String name, List<Address> addresses) { this.name = name; this.addresses = List.copyOf(addresses); // makes a copy of the list to protect from outside mutations (Java 10+). // Otherwise, use Collections.unmodifiableList(new ArrayList<>(addresses)); } public String getName() { return this.name; // String is immutable, okay to expose } public List<Address> getAddresses() { return addresses; // Address list is immutable } } public final class Address { // final class, can't be overridden by subclasses private final String city; // only immutable classes 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; } }
现在,可以使用以下代码:
import java.util.List; final Person person = new Person("John", List.of(new Address(“Sydney”, "Australia"));
现在,上面的代码是不可变的,但是由于Person 和 Address 类的设计 ,同时还有最终引用,因此无法将person变量重新分配给其他任何东西。
更新:正如有些人提到的,上面的代码仍然是可变的,因为我没有在构造函数中复制地址列表。因此,如果不在ArrayList() 构造函数中调用new ,仍然可以执行以下操作:
final List<Address> addresses = new ArrayList<>(); addresses.add(new Address("Sydney", "Australia")); final Person person = new Person("John", addressList); addresses.clear();
上述内容就是Java中不可变数据结构是什么,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。