Java 中的可變物件與不可變對象
一、簡介
在 Java 中使用物件時,了解可變物件和不可變物件之間的差異至關重要。這些概念影響 Java 程式碼的行為和設計。
在本教程中,我們將探討可變物件和不可變物件的定義、範例、優點和注意事項。
2. 不可變對象
不可變物件是指一旦建立其狀態就無法變更的物件。一旦不可變物件被實例化,它的值和屬性在其整個生命週期中保持不變。
讓我們探討一些 Java 中內建不可變類別的範例。
2.1. String
類
Java中Strings
的不可變性保證了執行緒安全,增強了安全性,並透過String
池機制幫助高效利用記憶體。
@Test
public void givenImmutableString_whenConcatString_thenNotSameAndCorrectValues() {
String originalString = "Hello";
String modifiedString = originalString.concat(" World");
assertNotSame(originalString, modifiedString);
assertEquals("Hello", originalString);
assertEquals("Hello World", modifiedString);
}
在此範例中, concat()
方法建立一個新的String
,而原始String
保持不變。
2.2. Integer
類
在 Java 中, Integer
類別是不可變的,這意味著它的值一旦設定就無法更改。但是,當您對Integer
執行操作時,會建立一個新實例來儲存結果。
@Test
public void givenImmutableInteger_whenAddInteger_thenNotSameAndCorrectValue() {
Integer immutableInt = 42;
Integer modifiedInt = immutableInt + 8;
assertNotSame(immutableInt, modifiedInt);
assertEquals(42, (int) immutableInt);
assertEquals(50, (int) modifiedInt);
}
這裡,+操作創建了一個新的Integer
對象,並且原始對象保持不可變。
2.3.不可變物件的優點
Java 中的不可變物件具有多種優勢,有助於提高程式碼可靠性、簡單性和效能。讓我們了解使用不可變物件的一些好處:
- 線程安全:不變性本質上保證了線程安全。由於不可變物件的狀態在創建後無法修改,因此可以在多個執行緒之間安全地共享它,而不需要明確同步。這簡化了並發編程並降低了競爭條件的風險。
- 可預測性和調試:不可變物件的恆定狀態使程式碼更具可預測性。創建後,不可變物件的值保持不變,從而簡化了程式碼行為的推理。
- 促進快取和最佳化:不可變物件可以輕鬆快取和重複使用。一旦創建,不可變物件的狀態就不會改變,從而允許高效的快取策略。
因此,開發人員可以在 Java 應用程式中使用不可變物件來設計更健壯、可預測且高效的系統。
3. 建立不可變對象
要建立不可變對象,讓我們考慮一個名為ImmutablePerson
的類別的範例。該類別被聲明為final
以防止擴展,並且它包含private final
字段,沒有setter方法,遵循不變性原則。
public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
現在,讓我們考慮一下當我們嘗試修改ImmutablePerson
實例的名稱時會發生什麼:
ImmutablePerson person = new ImmutablePerson("John", 30);
person.setName("Jane");
嘗試修改ImmutablePerson
實例的名稱將導致編譯錯誤。這是因為該類別被設計為不可變的,沒有允許在實例化後更改其狀態的 setter 方法。
setter 的缺失以及將類別聲明為final
確保了物件的不變性,從而提供了一種清晰而健壯的方法來處理整個生命週期中的常量狀態。
4. 可變對象
Java 中的可變物件是其狀態在建立後可以修改的實體。這種可變性引入了可變內部資料的概念,允許在物件的生命週期中更改值和屬性。
讓我們探討幾個例子來了解它們的特徵。
4.1. StringBuilder
類
Java 中的StringBuilder
類別表示可變的字元序列。與它的不可變對應物String
不同, StringBuilder
允許動態修改其內容。
@Test
public void givenMutableString_whenAppendElement_thenCorrectValue() {
StringBuilder mutableString = new StringBuilder("Hello");
mutableString.append(" World");
assertEquals("Hello World", mutableString.toString());
}
這裡, append
方法直接改變StringBuilder
物件的內部狀態,展示了它的可變性。
4.2. ArrayList
列表類
ArrayList
類別是可變物件的另一個範例。它表示一個動態數組,可以增大或縮小大小,從而允許添加和刪除元素。
@Test
public void givenMutableList_whenAddElement_thenCorrectSize() {
List<String> mutableList = new ArrayList<>();
mutableList.add("Java");
assertEquals(1, mutableList.size());
}
add
方法透過加入元素來修改ArrayList
的狀態,體現了其可變性質。
4.3.注意事項
雖然可變物件提供了靈活性,但開發人員需要注意它們的某些注意事項:
- 線程安全:可變物件可能需要額外的同步機制來確保多線程環境中的線程安全。如果沒有適當的同步,並發修改可能會導致意外的行為。
- 程式碼理解的複雜性:修改可變物件的內部狀態的能力引入了程式碼理解的複雜性。開發人員需要謹慎對待物件狀態的潛在更改,尤其是在大型程式碼庫中。
- 狀態管理挑戰:管理可變物件的內部狀態需要仔細考慮。開發人員應該追蹤和控制更改,以確保物件的完整性並防止意外修改。
儘管有這些考慮因素,可變物件提供了一種動態且靈活的方法,允許開發人員根據不斷變化的需求調整物件的狀態。
5. 可變對象與不可變對象
在對比可變物件和不可變物件時,有幾個因素會發揮作用。讓我們探討一下這兩類物件之間的根本差異:
標準 | 可變對象 | 不可變對象 |
---|---|---|
可修改性 | 創建後可以更改 | 創建後保持不變 |
線程安全 | 可能需要同步以確保線程安全 | 本質上線程安全 |
可預測性 | 可能會帶來理解上的複雜性 | 簡化推理和調試 |
性能影響 | 由於同步可能會影響效能 | 通常對性能有正面影響 |
5.1.在可變性和不變性之間進行選擇
可變性和不變性之間的選擇取決於應用程式的需求。如果需要適應性和頻繁更改,請選擇可變物件。然而,如果一致性、安全性和穩定狀態是優先考慮的因素,那麼不變性就是最佳選擇。
考慮多任務場景中的並發方面。不變性簡化了任務之間的資料共享,而無需複雜的同步。
此外,評估您的應用程式的效能需求。雖然不可變物件通常會提高效能,但請權衡這種提升是否比可變物件提供的靈活性更重要,尤其是在資料變更不頻繁的情況下。
保持適當的平衡可確保您的程式碼有效地滿足應用程式的需求。
六,結論
總之,Java 中可變物件和不可變物件之間的選擇對於塑造程式碼的可靠性、效率和可維護性起著至關重要的作用。不變性提供了線程安全性、可預測性和其他優勢,而可變性則提供了靈活性和動態狀態變化。
評估應用程式的需求並考慮並發性、效能和程式碼複雜性等因素將有助於為設計彈性且高效的 Java 應用程式做出適當的選擇。
您可以在 GitHub 上找到本文中使用的範例。