[英]How to make object immutable in java
由於這是當前的一個熱門話題,我無法理解某些概念。 請原諒我,如果我聽起來很愚蠢,但當我嘗試創建不可變對象的大部分帖子時,我發現了以下幾點
現在我無法理解為什么我們需要以下幾點
我在這里添加我的課程和測試用例:
public final class ImmutableUser {
private final UUID id;
private final String firstName;
private final String lastName;
public ImmutableUser(UUID id, String firstName, String lastName) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
/**
* @return the id
*/
public UUID getId() {
return id;
}
/**
* @return the firstName
*/
public String getFirstName() {
return firstName;
}
/**
* @return the lastName
*/
public String getLastName() {
return lastName;
}
}
測試用例
public class ImmutableUserTest {
@Test(expected = IllegalAccessException.class)
public void reflectionFailure() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
ImmutableUser user = new ImmutableUser(UUID.randomUUID(), "john", "liu");
Field i =user.getClass().getDeclaredField("firstName");
i.setAccessible(true);
i.set(user, "cassandra");
System.out.println("user " + user.getFirstName()); // prints cassandra
}
}
此測試用例失敗並打印cassandra。
如果我做錯了,請告訴我。
我從final
制作屬性開始。 使屬性final
保證您不能更改屬性值。 我認為這是顯而易見的。 (我將在以后更改引用不可變對象的內容時寫下額外的注釋)。
現在,當所有屬性都是final
屬性時,必須通過構造函數啟動它們。 但是有些類有很多屬性,所以構造函數變得很大。 此外,有時可以將某些屬性初始化為默認值。 嘗試支持這一點會導致我們使用幾乎隨機的參數組合來實現幾個構造函數。 然而,Builder模式有助於我們。 但是如何讓用戶使用Builder而不是直接調用構造函數呢? 答案是使構造函數private
並創建返回構建器的靜態方法:
public class Person {
private final String firstName;
private final String lastName;
private final Person mother;
private final Person father;
private Person(String firstName, String lastName, Person mother, Person father) {
// init the fields....
}
public static PersonBuilder builder() {
return new PersonBuilder();
}
public static class PersonBuilder {
// here fields are NOT final
private String firstName;
private String lastName;
private Person mother;
private Person father;
public PersonBuilder bornBy(Person mother) {
this.mother = mother;
return this;
}
public PersonBuilder conceivedBy(Person father) {
this.father = father;
return this;
}
public PersonBuilder named(String firstName) {
this.firstName = firstName;
return this;
}
public PersonBuilder fromFamily(String lastName) {
this.lastName = lastName;
return this;
}
Person build() {
return new Person(name, lastName, mother, father);
}
}
}
這是典型的使用模式:
Person adam = Person.builder().named("Adam").build(); // no mother, father, family
Person eve = Person.builder().named("Eve").build(); // no mother, father, family
Person cain = Person.builder().named("Cain").conerivedBy(adam).bornBy(eve); // this one has parents
正如您所看到的那樣,構建器模式通常比工廠更好,因為它更靈活。
我認為你在問題中錯過了一點:對其他(可變)對象的引用。 例如,如果我們在Person
類中添加字段Collection<Person> children
Person
我們必須關心getChildren()
返回Iterable
或至少是不可修改的集合。
回答 :使構造函數createInstance()
私有並提供createInstance()
(工廠方法)本身並沒有幫助:為了讓用戶在你仍然可以控制的情況下實際使用類及其實例,這是你應該做的一件事。實例的創建方式。
答 :將類聲明為final
意味着用戶無法擴展它,因此它會“阻止”用戶使用這種“解決方法”。 將屬性聲明為final
將不允許類的用戶更改它。 它不能“意外修改”,但可以使用反射“惡意修改”。 讓我們看一個例子,說你有:
final public class SomeClass {
final Integer i = 1;
}
從另一個班級你可以做如下:
class AnotherClass {
public static void main (String[] args) throws Exception {
SomeClass p = new SomeClass();
Field i =p.getClass().getDeclaredField("i");
i.setAccessible(true);
i.set(p, 5);
System.out.println("p.i = " + p.i); // prints 5
}
}
答 :您可以使用構建器模式或任何可幫助您控制類實例創建的模式。
進一步:
如果要確保您的類是不可變的,請確保任何getter
返回類成員的深層副本 。 這種技術被稱為“保護/防御性復制”。 你可以在這里閱讀更多相關信息
使構造函數成為私有的並使用構建器模式對於不可變性不是必需的。 但是因為你的類不能提供setter並且它有很多字段,所以使用帶有許多參數的構造函數可能對可讀性有害,因此使用構建器模式(需要pervade構造函數)的想法。
其他答案似乎錯過了重要的一點。
使用final字段是必不可少的,不僅要確保它們不被修改,還要因為否則會丟失一些重要的線程安全保證。 實際上,不變性的一個方面是它為您帶來了線程安全性。 如果你沒有使字段成為最終,那么你的類就變得有效了 。 例如,參見必不可變對象的所有屬性是否必須是最終的?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.