我的java类是不可变的吗

我的java类是不可变的吗,java,immutability,Java,Immutability,请看,我并不是在问什么是不变性,我理解不变性,但问题更多的是,当您引用一个可变对象时,如何创建一个不可变类。此外,我的类没有通过可变检测器项目的检查,因此请求您查看 我创建了一个不可变的类EmpAndAddress.java,它引用了一个可克隆的可变类EmpAddress.java。 我遵循java规则,尝试使用可变检测器测试我的类,但我的类没有通过不可变测试。我只是想看看我是否遗漏了什么。在我的Immutable中,我总是创建EmpAddress类型的新对象,以遵循规则 1.可变EmpAdd

请看,我并不是在问什么是不变性,我理解不变性,但问题更多的是,当您引用一个可变对象时,如何创建一个不可变类。此外,我的类没有通过可变检测器项目的检查,因此请求您查看

我创建了一个不可变的类EmpAndAddress.java,它引用了一个可克隆的可变类EmpAddress.java。 我遵循java规则,尝试使用可变检测器测试我的类,但我的类没有通过不可变测试。我只是想看看我是否遗漏了什么。在我的Immutable中,我总是创建EmpAddress类型的新对象,以遵循规则

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();
    }

}

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

在任何情况下,为了确保类在引用可变对象时是不可变的,可以在构造函数中接收
EmpAddress
实例时以及从
geteAddr()
方法返回实例时执行克隆

您已经在
geteAddr()
方法中完成了这项工作,因此在这方面还可以

您所缺少的只是修复构造函数,如下所示:

public EmpAndAddress(int empId,String empName,EmpAddress eAddr){
    this.empId = empId;
    this.empName = empName;
    this.eAddr = (EmpAddress) eAddr.clone();    
}
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();
    }
}

MutabilityDetector代码正在检查类是否是可传递不可变的。类本身不可变是不够的。所有类字段的类型也必须是不可变的。字段引用的子对象被假定为父对象状态的一部分,因此更改子对象会更改父对象


在您的例子中,(假定)不可变类
EmpAndAddress
有一个类型可变的字段。此外,
EmpAndAddress
对象中的字段使用作为构造函数参数传递的值进行初始化。如果构造函数的调用方保留
EmpAddress
引用,它可以更改
EmpAddress
对象的状态

全面披露:这里是易变性检测器的作者。。。用一个愚蠢的回答

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

@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,但现在忽略它。)

对这个问题的回答是完全正确的
EmpAndAddress
被认为是可变的,因为
EmpAndAddress
被认为是可变的,并且不可变对象的每个字段也应该是不可变的
EmpAddress
是可变的,原因有几个:可以子类化;具有公共非最终字段;setter方法。第一个问题是,为什么克隆getter中的
EmpAddress
字段无助于使其不可变?好吧,在这种情况下,它确实使它不可变,但可变检测器并没有执行对它有信心所需的那种分析。在允许可变对象“逃逸”到调用方时,可变检测器没有任何特殊的分析来安全地克隆可变对象。这是因为它很容易被误用
.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
}

那么,该怎么办呢?有两种选择

方法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库)。在这种情况下,您可以创建如下不可变适配器:

@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);
    }
}
然后在
EmpAndAddress
中可以如下所示:

public EmpAndAddress(int empId,String empName,EmpAddress eAddr){
    this.empId = empId;
    this.empName = empName;
    this.eAddr = (EmpAddress) eAddr.clone();    
}
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
有一个不可变的字段,并且不依赖于
对象的脆弱行为。clone

如果您想使一个类不可变,这也是一种很好的技术,但是您必须进行许多代码更改才能做到这一点。这允许您在代码库中越来越多的地方逐渐引入一个不可变的版本,直到最终原始的可变类只在系统的边缘使用,甚至完全消失


我希望这个anwser和可变检测器在学习如何创建不可变对象方面很有用。

谢谢,我之前尝试过,并在构造函数中重试了以下所有选项,但是使用可变检测器检查失败。(我不确定可变检测器是否准确。)public EmpAndAddress(int-empId,String-empName,EmpAddress-eAddr)抛出CloneNotSupportedException{super();this.empId=empId;this.empName=empName;this.eAddr=(EmpAddress)eAddr.clone();}