披露:可變性探測器這裏的作者......用過長...答案。
如果我們從您在問題中定義的類開始,並斷言它是不可變的,就像這樣;
@Test
public void isImmutable() {
assertImmutable(EmpAndAddress.class);
}
您出現以下消息的測試失敗:
org.mutabilitydetector.unittesting.MutabilityAssertionError:
Expected: org.mutabilitydetector.stackoverflow.Question_32020847$EmpAndAddress to be IMMUTABLE
but: org.mutabilitydetector.stackoverflow.Question_32020847$EmpAndAddress is actually NOT_IMMUTABLE
Reasons:
Field can have a mutable type (org.mutabilitydetector.stackoverflow.Question_32020847$EmpAddress) assigned to it. [Field: eAddr, Class: org.mutabilitydetector.stackoverflow.Question_32020847$EmpAndAddress]
Allowed reasons:
None.
(我定義了兩個類爲靜態內部類,因此顯示爲$ EmpAndAddress消息中,但忽略)
Another answer這個問題是完全正確的。 EmpAndAddress
被認爲是可變的,因爲EmpAddress
被認爲是可變的,並且不可變對象的每個字段也應該是不可變的。 EmpAddress
由於幾個原因是可變的:可以被分類;有公共的非決賽場地; setter方法。第一個問題是,爲什麼克隆getter中的EmpAddress
字段不能使其不可變?那麼,在這種情況下,它確實會使其不可變,但Mutability Detector不會執行所需的分析以確保它的可信度。當讓他們「逃離」給呼叫者時,Mutability Detector沒有任何特殊分析來安全地克隆可變對象。這是因爲很容易誤用.clone
並引入可變性。試想一下,如果EmpAddress
有一個可變的領域,像List
,你可以觀察到突變,像這樣:
@Test
public void go_on_try_to_mutate_me_now_i_have_a_list_field() throws Exception {
EmpAndAddress e = new EmpAndAddress(1234, "John Doe");
assertThat(e.geteAddr().getMyList(), is(Collections.<String>emptyList()));
e.geteAddr().getMyList().add("Haha, I'm mutating you");
assertThat(e.geteAddr().getMyList(), is(Collections.<String>emptyList())); // Fails because list now has one element in it
}
這是因爲Object.clone
不執行深層副本。在這種情況下,這只是安全的,因爲在EmpAddress
中克隆的字段是不可變的(String
和基本int
)。可變性檢測器可以嘗試識別.clone
的安全用法,但它可能非常脆弱。因爲它不能確定你的課程是不可變的,所以Mutability Detector認爲它是可變的。
創建一個新的EmpAddress類型的對象有助於使類不可變,這是對的,因爲它「保護」實例,使其保持私有狀態,其他代碼無法訪問它。如果構造函數接受一個實例並將其分配給一個字段,則將參數傳遞給構造函數的任何人都可以對其進行修改,從而將任何使用該實例的實例變爲字段。就像這個例子:
@Test
public void mutate_object_by_giving_it_a_parameter_then_modifying_it() throws Exception {
EmpAddress empAddress = new EmpAddress("New York", 1234);
EmpAndAddress e = new EmpAndAddress(1234, "John Doe", empAddress);
assertThat(e.geteAddr().getCity, is("New York"));
empAddress.setCity("Haha, I'm mutating you");
assertThat(e.geteAddr().getCity(), is("New York")); // fails because city has been changed
}
那麼,該怎麼辦呢?有幾個選項。
方法1:覆蓋可變性探測器,因爲你知道更好
更改您的測試增加一個「允許的理由」是可變的。也就是說,你滿意的失敗是一個誤報,你想抓住其他潛在的錯誤引入可變性,但忽略這種情況。要做到這一點,添加如下代碼:
@Test
public void isImmutable_withAllowedReason() {
assertInstancesOf(EmpAndAddress.class, areImmutable(),
AllowedReason.assumingFields("eAddr").areNotModifiedAndDoNotEscape());
}
你把這個選項前,你應該非常肯定這一點,如果你是新的不可變對象,我建議不這樣做,這樣就可以了學會更安全地創建不可變對象。
方法2:EmpAddress
不變爲好,像這樣:
@Immutable
public static final class ImmutableEmpAddress {
private final String empCity;
private final int zipCode;
public ImmutableEmpAddress(String empCity, int zipCode) {
this.empCity = empCity;
this.zipCode = zipCode;
}
public String getEmpCity() { return empCity; }
public int getZipCode() { return zipCode; }
}
然後當你從EmpAndAddress
返回它作爲一個字段,你不需要克隆它。這將是一個理想的解決方案。
方法3:創建一個不可變的適配器
然而,在某些情況下,你不能讓EmpAddress
一成不變的。也許代碼位於不同的庫中,或者由需要使用反射設置字段的框架(如Hibernate或其他JavaBean庫)使用它。在EmpAndAddress
@Immutable
public static final class ImmutableEmpAddressAdapter {
public final String empCity;
public final int zipCode;
public ImmutableEmpAddressAdapter(EmpAddress mutableAddress) {
// perform a deep copy of every field on EmpAddress that's required to re-construct another instance
this.empCity = mutableAddress.getEmpCity();
this.zipCode = mutableAddress.getZipCode();
}
public EmpAddress getEmpAddress() {
return new EmpAddress(this.empCity, this.zipCode);
}
}
然後看起來是這樣的:在這種情況下,你可以像這樣創建一個不可改變的適配器
public static final class EmpAndAddress {
// some code ommitted for brevity
private final ImmutableEmpAddressAdapter immutableEmpAddressAdapter;
public EmpAndAddress(int empId, String empName){
this.immutableEmpAddressAdapter = new ImmutableEmpAddressAdapter(new EmpAddress(" ", -1));
}
public EmpAddress geteAddr() {
return immutableEmpAddressAdapter.getEmpAddress();
}
}
雖然這種技術將需要更多的代碼,這使得它非常明確地指定其他讀者這個類EmpAddress
有一個不變的領域,並且不依賴於Object.clone
這也是一個很好的技術,如果你想使一類不可變的脆弱的行爲,但你必須做出許多鱈魚e改變做它。這使您可以逐漸在代碼庫中的越來越多的地方引入不可變的版本,直到最終的原始可變類僅用於系統的邊緣,甚至完全消失。
我希望這個anwser和Mutability Detector在學習如何創建不可變對象方面很有用。
謝謝你,我曾經嘗試過,並在構造函數中重試了以下所有選項,但是使用可變性檢測器檢查失敗(我不確定可變性檢測器是否準確。)\t public EmpAndAddress(int empId,String empName,EmpAddress eAddr)拋出CloneNotSupportedException異常{ \t \t super(); \t \t this.empId = empId; \t \t this.empName = empName; \t \t \t \t this.eAddr =(EmpAddress)eAddr.clone(); \t} – AnuragM