2015-08-15 42 views
-1

請參閱我不是問什麼是不可改變的,我明白不變性,但問題是,更多的如何使一個不可變類,當你到一個可變的對象,提供參考。而且我的班級沒有通過可變性探測器項目的檢查,因此請求你的看法。是我的Java類不可變

我已經創建了一個不可變類EmpAndAddress.java並且它具有參照可變類EmpAddress.java這是可複製的。 我遵循了java規則,並嘗試使用可變性檢測器來測試我的類,但是我的類未通過不可變測試。只是想檢查我是否缺少一些東西。在我的Immutable中,我總是創建一個新的EmpAddress類型的對象,使其可以遵循規則。

http://mutabilitydetector.github.io/MutabilityDetector/ 1.易變EmpAddress.java

public class EmpAddress implements Cloneable{ 
    public String empCity; 
    public int zipCode; 

    public EmpAddress(String empCity, int zipCode) { 
     super(); 
     this.empCity = empCity; 
     this.zipCode = zipCode; 
    } 

    public String getEmpCity() { 
     return empCity; 
    } 

    public void setEmpCity(String empCity) { 
     this.empCity = empCity; 
    } 

    public int getZipCode() { 
     return zipCode; 
    } 

    public void setZipCode(int zipCode) { 
     this.zipCode = zipCode; 
    } 

    protected Object clone() throws CloneNotSupportedException {   
     EmpAddress clone=(EmpAddress)super.clone(); 
     return clone;  
     } 
} 


public final class EmpAndAddress implements Cloneable { 
    private final int empId; 
    private final String empName; 
    private final EmpAddress eAddr; 

    public EmpAndAddress(int empId,String empName,EmpAddress eAddr){ 
     super(); 
     this.empId = empId; 
     this.empName = empName; 
     this.eAddr = new EmpAddress(" ", -1);  
    } 

    public int getEmpId() { 
     return empId; 
    } 


    public String getEmpName() { 
     return empName; 
    } 

    public EmpAddress geteAddr() throws CloneNotSupportedException { 
     return (EmpAddress) eAddr.clone(); 
    } 

} 
+0

謝謝你,我曾經嘗試過,並在構造函數中重試了以下所有選項,但是使用可變性檢測器檢查失敗(我不確定可變性檢測器是否準確。)\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

回答

1

我看到的唯一的問題是,你實際上沒有使用EmpAddress實例傳遞給EmpAndAddress構造。我懷疑你是有意的。

在任何情況下,爲了確保你的類是儘管以一個可變對象的引用不可變是通過進行克隆兩者在構造接收EmpAddress實例時,以及從geteAddr()方法返回一個實例時。

你已經做了geteAddr()方法裏面,所以你在這一方面確定。

所有你缺的是固定你的構造,就像這樣:

public EmpAndAddress(int empId,String empName,EmpAddress eAddr){ 
    this.empId = empId; 
    this.empName = empName; 
    this.eAddr = (EmpAddress) eAddr.clone();  
} 
1

的MutabilityDetector代碼檢查類是及物動詞不變。這個類本身是不可變的是不夠的。所有類字段的類型也必須是不可變的。由字段引用的子對象被假定爲父對象狀態的一部分,因此更改子對象會更改父對象。

在你的情況下,(據說)不可變類EmpAndAddress有一個字段,它的類型是可變的。此外,EmpAndAddress對象中的字段使用作爲構造函數參數傳遞的值進行初始化。如果構造函數的調用者保持EmpAddress參考,它可以改變EmpAndAddress對象的狀態。

0

披露:可變性探測器這裏的作者......用過長...答案。

如果我們從您在問題中定義的類開始,並斷言它是不可變的,就像這樣;

@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在學習如何創建不可變對象方面很有用。