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