为什么数据结构不用java_泛谈Java中的不可变数据结构
作為我最近一直在進行的一些編碼訪談的一部分,有時會出現不變性問題。我自己并不過分教條,但每當不需要可變狀態時,我會試圖擺脫導致可變性的代碼,這在數據結構中通常是最明顯的。然而,似乎對不可變性的概念存在一些誤解,開發人員通常認為擁有final引用,或者val在Kotlin或Scala中,足以使對象不可變。這篇博客文章深入研究了不可變引用和不可變數據結構。
不可變數據結構的好處
不可變數據結構具有顯著優勢,例如:
沒有無效的狀態
線程安全
易于理解的代碼
更容易測試代碼
可用于值類型
沒有無效的狀態
當一個對象是不可變的時,很難讓對象處于無效狀態。該對象只能通過其構造函數實例化,這將強制對象的有效性。這樣,可以強制執行有效狀態所需的參數。一個例子:
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");
無論最終修飾符如何,這都是一件非常容易的事情?;蛘?#xff0c; 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
addresses;public Person(String name, List
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
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
addresses = new ArrayList<>();addresses.add(new Address("Sydney", "Australia"));
final Person person = new Person("John", addressList);
addresses.clear();
但是,由于在構造函數中創建了一個新副本,上面的代碼將不再影響類中復制的地址列表引用Person ,從而使代碼安全。
我希望上述內容有助于理解最終和不變性之間的差異。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
總結
以上是生活随笔為你收集整理的为什么数据结构不用java_泛谈Java中的不可变数据结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用tcc编译一个c语言写的简单代码hel
- 下一篇: Java 父子类方法调用顺序