我知道使我们的类不可变的所有基本规则但是当有另一个类引用时我有点困惑.我知道如果有集合而不是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; } }
markspace.. 10
好吧,这个概念是阅读JLS并理解它.在这种情况下,JLS说:
final字段还允许程序员在没有同步的情况下实现线程安全的不可变对象.线程安全的不可变对象被所有线程视为不可变的,即使使用数据争用传递线程之间的不可变对象的引用也是如此.这可以提供安全保证,防止错误或恶意代码滥用不可变类.必须正确使用最终字段以提供不可变性的保证.
最终字段的使用模型很简单:在该对象的构造函数中设置对象的最终字段; 并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方写入对正在构造的对象的引用.如果遵循此原因,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本.它还将看到那些最终字段引用的任何对象或数组的版本,这些字段至少与最终字段一样是最新的.
所以你需要:
使address
最终和私人.
对于任何可变对象,必须防止从外部看到对该对象的引用.
在这种情况下,#2可能意味着你不能像你一样返回对地址的引用getAddress()
. 而且你必须在构造函数中制作一个防御性副本.即,复制任何可变参数,并将副本存储在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
是一个String,字符串是不可变的.如果street
由可变字段(例如整数街道号码)组成,那么我将不得不制作副本street
,等等无限制地.这就是为什么不可变对象如此有价值,它们打破了"无限复制"链.
好吧,这个概念是阅读JLS并理解它.在这种情况下,JLS说:
final字段还允许程序员在没有同步的情况下实现线程安全的不可变对象.线程安全的不可变对象被所有线程视为不可变的,即使使用数据争用传递线程之间的不可变对象的引用也是如此.这可以提供安全保证,防止错误或恶意代码滥用不可变类.必须正确使用最终字段以提供不可变性的保证.
最终字段的使用模型很简单:在该对象的构造函数中设置对象的最终字段; 并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方写入对正在构造的对象的引用.如果遵循此原因,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本.它还将看到那些最终字段引用的任何对象或数组的版本,这些字段至少与最终字段一样是最新的.
所以你需要:
使address
最终和私人.
对于任何可变对象,必须防止从外部看到对该对象的引用.
在这种情况下,#2可能意味着你不能像你一样返回对地址的引用getAddress()
. 而且你必须在构造函数中制作一个防御性副本.即,复制任何可变参数,并将副本存储在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
是一个String,字符串是不可变的.如果street
由可变字段(例如整数街道号码)组成,那么我将不得不制作副本street
,等等无限制地.这就是为什么不可变对象如此有价值,它们打破了"无限复制"链.
那么Java文档提供了一些步骤
一种定义不可变对象的策略
以下规则定义了用于创建不可变对象的简单策略.并非所有记录为"不可变"的类都遵循这些规则.这并不一定意味着这些课程的创造者是草率的 - 他们可能有充分的理由相信他们的课程实例在建造后永远不会改变.但是,这种策略需要复杂的分析,不适合初学者.
不要提供"setter"方法 - 修改字段引用的字段或对象的方法.
使所有字段成为最终和私有.
不允许子类重写方法.最简单的方法是将类声明为final.更复杂的方法是使构造函数私有并在工厂方法中构造实例.
如果实例字段包含对可变对象的引用,则不允许更改这些对象:
不要提供修改可变对象的方法.
不要共享对可变对象的引用.永远不要存储对传递给构造函数的外部可变对象的引用; 如有必要,创建副本并存储对副本的引用.同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象.
地址类是可变的,因为您可以使用setStreet方法对其进行修改.所以其他类可以修改这个类.
我们可以通过传递Address实例的副本来防止这种情况,而不是信任对我们给出的实例的引用.
使Address对象最终
private final Address address;
其次,
this.address = new Address(address.getStreet());
在Address类中创建构造函数,为Street设置Street.Remove setter方法.
最后代替
public Address getAddress(){ return address; }
使用
public Address getAddress(){ return new Address(address.getStreet()); }