当前位置: 首页 > 面试题库 >

我们如何使用可变引用来维护类的不变性

赫连卓
2023-03-14
问题内容

我知道使我们的类不可变的所有基本规则,但是当有另一个类引用时,我有点困惑。我知道是否有集合而不是集合,Address然后我们可以利用它,Collections.unmodifiableList(new ArrayList<>(modifiable));然后使我们的类不可变。但是在下面的情况下,我仍然无法理解这个概念。

public final class Employee{
    private final int id;
    private Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=address;
    }
    public int getId(){
        return id;
    }
    public Address getAddress(){
        return address;
    }
}

public class Address{
    private String street;
    public String getStreet(){
        return street;
    }
    public void setStreet(String street){
        this.street = street;
    }
}

问题答案:

好吧,这个概念是阅读并了解JLS。在这种情况下,JLS说:

final字段还允许程序员无需同步即可实现线程安全的不可变对象。即使所有线程都使用数据竞争在线程之间传递对不可变对象的引用,线程安全的不可变对象也被所有线程视为不可变的。这可以提供安全保证,以防止由于错误或恶意代码而滥用不可变类。必须正确使用final字段以保证不变性。

final字段的使用模型很简单:在该对象的构造函数中为该对象设置final字段;并且不要在对象的构造函数完成之前,在另一个线程可以看到它的地方编写对正在构造的对象的引用。如果执行此操作,则当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。它还将查看那些最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样。

因此,您需要:

  1. 设置address最终版和私有版。
  2. 对于任何可变对象,必须防止从外部看到对该对象的引用。

在这种情况下,#2可能意味着您无法像对with一样返回对Address的引用getAddress()而且
您必须在构造函数中进行防御性复制。即,制作任何可变参数的副本,并将副本存储在Employee中。如果您不能制作防御性副本,那么实际上就没有办法使Employee不可变。

public final class Employee{
    private final int id;
    private final Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=new Address();  // defensive copy
        this.address.setStreet( address.getStreet() );
    }
    public int getId(){
        return id;
    }
    public Address getAddress() {
        Address nuAdd = new Address(); // must copy here too
        nuAdd.setStreet( address.getStreet() );
        return nuAdd;
}

实现clone()或类似的操作(复制ctor)将使为复杂的类创建防御对象更加容易。但是,我认为最好的建议是使其Address不可变。完成后,您可以自由传递其引用,而不会出现任何线程安全问题。

在这个例子中,通知我 不是 要复制的价值street
Street是一个字符串,并且字符串是不可变的。如果street包含可变字段(例如,整数街道编号),那么我
必须复制一个副本street,以此类推。这就是为什么不可变对象如此有价值,它们打破了“无限复制”链条的原因。



 类似资料:
  • 问题内容: 此方法不正确。我试图找出的是一种不知道将存在哪些cookie,能够处理cookie更改以及维护会话的方法。 我正在为我的简单机器论坛编写一个应用程序,当您单击某些自定义行为时,它会更改其cookie配置。 但是,如果该应用程序对我的网站运行良好,我将发布一个供其他论坛使用的版本。 我知道我朝着正确的方向前进,但是逻辑有点像在踢我的屁股。 任何建议将不胜感激。 问题答案: 这段代码很混乱

  • 问题内容: 我想知道我们是否可以使用索引访问列表 例如: 问题答案: 由于是运算符,而Java不支持运算符重载,因此您不能将其与List一起使用。相反,您必须使用和方法,这些方法可能很冗长,但提供的功能完全相同。

  • 从…起http://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html : 不要提供“setter”方法——修改字段或字段引用的对象的方法。使所有字段都是最终的和私有的。不要允许子类重写方法。最简单的方法是将类声明为最终的。一个更复杂的方法是使构造函数私有,并在工厂方法中构造实例。如果实例字段包括对可变对象的引用,不

  • 我理解不可变意味着它是一个实例化后不会改变状态的对象。但在这行代码中,我看不到数组值何时被声明为Final。这个类是不变的吗?谁能解释一下怎么找到答案吗。谢谢

  • 我正在研究Joshua Bloch的Effective Java,他在书中解释了实现不可变类的不同方法。为了防止子类化,一种方法是使用 final。更复杂的方法是使构造函数私有,从而防止外部访问,并使用静态工厂创建对象。 但是,我不明白这个说法: 它最灵活,因为它允许使用多个包私有实现类。 我知道在没有公共/受保护的构造函数的情况下,外部客户端不可能对其进行子类化,但不了解术语“多包私有实现类”所