是我的Java类不可变

问题描述:

请参阅我不是问什么是不可改变的,我明白不变性,但问题是,更多的如何使一个不可变类,当你到一个可变的对象,提供参考。而且我的班级没有通过可变性探测器项目的检查,因此请求你的看法。是我的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

我看到的唯一的问题是,你实际上没有使用EmpAddress实例传递给EmpAndAddress构造。我怀疑你是有意的。

在任何情况下,为了确保你的类是尽管以一个可变对象的引用不可变是通过进行克隆两者在构造接收EmpAddress实例时,以及从geteAddr()方法返回一个实例时。

你已经做了geteAddr()方法里面,所以你在这一方面确定。

所有你缺的是固定你的构造,就像这样:

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

的MutabilityDetector代码检查类是及物动词不变。这个类本身是不可变的是不够的。所有类字段的类型也必须是不可变的。由字段引用的子对象被假定为父对象状态的一部分,因此更改子对象会更改父对象。

在你的情况下,(据说)不可变类EmpAndAddress有一个字段,它的类型是可变的。此外,EmpAndAddress对象中的字段使用作为构造函数参数传递的值进行初始化。如果构造函数的调用者保持EmpAddress参考,它可以改变EmpAndAddress对象的状态。

披露:可变性探测器这里的作者......用过长...答案。

如果我们从您在问题中定义的类开始,并断言它是不可变的,就像这样;

@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在学习如何创建不可变对象方面很有用。