我们如何使用可变引用保持 class 的不变性
How can we maintain Immutability of a class with a mutable reference
我知道使我们的 class 不可变的所有基本规则,但是当有另一个 class 引用时我有点困惑。我知道如果有集合而不是 Address
那么我们可以使用 Collections.unmodifiableList(new ArrayList<>(modifiable));
然后我们可以使我们的 class 不可变。但在下面的情况下,我仍然无法理解。
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;
}
}
所以在你的例子中 Employee
class 是不可变的,因为一旦它被创建,你就不能改变它的状态,因为它只有 getter 个方法。
Address
class是mutable
,因为可以用setStreet
的方法修改。
因此,如果您有其他 class 使用 Address
对象,您确定 class 无法修改对象状态。
嗯,概念就是阅读JLS,理解它。 JLS 的第 17 章“线程和锁”描述了内存可见性和同步。 Section 17.5 "Final Field Semantics" 描述了 final 字段的内存可见性语义。该部分部分说:
final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code. final fields must be used correctly to provide a guarantee of immutability.
The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.
所以你需要:
- 使
address
成为最终的和私有的。
- 对于任何可变对象,您必须防止外部看到对该对象的引用。
在这种情况下,#2 可能意味着您不能像 getAddress()
那样 return 对地址的引用。 并且你必须在构造函数中创建一个防御性副本。即,复制任何可变参数,并将副本存储在 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()
或类似的东西(复制构造器)将使创建防御对象更容易复杂类。但是,我认为最好的建议是使 Address
不可变。一旦你这样做了,你就可以自由地传递它的引用,而不会出现任何线程安全问题。
在这个例子中,注意我不需要复制street
的值。 Street
是一个字符串,字符串是不可变的。如果 street
包含可变字段(例如整数街道号码),那么我 将 也必须制作 street
的副本,依此类推。这就是不可变对象如此有价值的原因,它们打破了“无限复制”链。
由于这个问题越来越受欢迎,我还应该提到 Brian Goetz's book, Java 并发实践, 这就是我了解这些的方式技术,我基本上是在解释上面那本书。
好吧,Java docs
提供了步骤
A Strategy for Defining Immutable Objects
The following rules define a simple strategy for creating immutable
objects. Not all classes documented as "immutable" follow these rules.
This does not necessarily mean the creators of these classes were
sloppy — they may have good reason for believing that instances of
their classes never change after construction. However, such
strategies require sophisticated analysis and are not for beginners.
- Don't provide "setter" methods — methods that modify fields or objects
referred to by fields.
- Make all fields final and private.
- Don't allow
subclasses to override methods. The simplest way to do this is to
declare the class as final. A more sophisticated approach is to make
the constructor private and construct instances in factory methods.
- If
the instance fields include references to mutable objects, don't allow
those objects to be changed:
- Don't provide methods that modify the
mutable objects.
- Don't share references to the mutable objects. Never
store references to external, mutable objects passed to the
constructor; if necessary, create copies, and store references to the
copies. Similarly, create copies of your internal mutable objects when
necessary to avoid returning the originals in your methods.
地址 class 是可变的,因为您可以使用 setStreet 方法修改它。
所以其他class可以修改这个class.
我们可以通过在传入 Address 实例时复制它来防御这种情况,而不是信任对给定实例的引用。
使 Address 对象成为最终对象
private final Address address;
其次,
this.address = new Address(address.getStreet());
在地址 class 中创建构造函数,为街道设置 Street.Remove setter 方法。
最后代替
public Address getAddress(){
return address;
}
使用
public Address getAddress(){
return new Address(address.getStreet());
}
如果你想把一个可变对象封装成一个不可变对象,那么你需要:
创建可变对象的副本(即通过复制构造函数、克隆、serialization/deserialization等);
永远不要存储对原始可变对象的引用。
从不 return 可变对象。如果必须,则 return 对象的副本。
避免可以更改可变对象的方法。
public Employee(int id, Address address){
this.id = id;
this.address=new Address();
this.address.setStreet( address.getStreet() );
}
public Address getAddress() {
Address nuAdd = new Address(); // must copy here too
nuAdd.setStreet( address.getStreet() );
return nuAdd;
}
你也可以通过克隆来使用浅拷贝
public final class Employee{
private final int id;
private Address address;
public Employee(int id, Address address)
{
this.id = id;
this.address=address.clone();
}
public int getId(){
return id;
}
public Address getAddress(){
return address.clone();
}
}
使用它会在 Employee class 中创建一个单独的 Address 对象,因此在这种情况下,对在 Employee 构造函数中作为参数传递的 Address 对象所做的任何更改都不会更改 Employee [= 的成员变量 Address 对象21=].
getAddress() 方法也返回一个克隆对象,因此对该方法获取的对象所做的任何更改都不会影响 Employee class 的地址对象。
注:
要使用此功能,请使地址 class 可克隆。
在 Employee class 中,return getAddress() 方法中地址实例的深度克隆副本,而不是 returning 原始实例。这样,如果有人更改了地址实例,它就无法反映原始实例。但有一个条件,Address class 必须实现 Cloneable 接口。
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() throws CloneNotSupportedException{
return (Address) address.clone();
}
}
What is difference between the creating the object of employee calling the clonne method.
1) 在构造函数和getter方法中创建员工对象并通过在构造函数中创建员工对象并将员工的必填字段设置到其中来设置所需的值,如下所示。
Example :
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;
}
2) cloning the employee object inside constructor and getter method as below.
Example :
public final class Employee{
private final int id;
private Address address;
public Employee(int id, Address address)
{
this.id = id;
this.address=address.clone();
}
public int getId(){
return id;
}
public Address getAddress(){
return address.clone();
}
}
我知道使我们的 class 不可变的所有基本规则,但是当有另一个 class 引用时我有点困惑。我知道如果有集合而不是 Address
那么我们可以使用 Collections.unmodifiableList(new ArrayList<>(modifiable));
然后我们可以使我们的 class 不可变。但在下面的情况下,我仍然无法理解。
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;
}
}
所以在你的例子中 Employee
class 是不可变的,因为一旦它被创建,你就不能改变它的状态,因为它只有 getter 个方法。
Address
class是mutable
,因为可以用setStreet
的方法修改。
因此,如果您有其他 class 使用 Address
对象,您确定 class 无法修改对象状态。
嗯,概念就是阅读JLS,理解它。 JLS 的第 17 章“线程和锁”描述了内存可见性和同步。 Section 17.5 "Final Field Semantics" 描述了 final 字段的内存可见性语义。该部分部分说:
final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code. final fields must be used correctly to provide a guarantee of immutability.
The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.
所以你需要:
- 使
address
成为最终的和私有的。 - 对于任何可变对象,您必须防止外部看到对该对象的引用。
在这种情况下,#2 可能意味着您不能像 getAddress()
那样 return 对地址的引用。 并且你必须在构造函数中创建一个防御性副本。即,复制任何可变参数,并将副本存储在 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()
或类似的东西(复制构造器)将使创建防御对象更容易复杂类。但是,我认为最好的建议是使 Address
不可变。一旦你这样做了,你就可以自由地传递它的引用,而不会出现任何线程安全问题。
在这个例子中,注意我不需要复制street
的值。 Street
是一个字符串,字符串是不可变的。如果 street
包含可变字段(例如整数街道号码),那么我 将 也必须制作 street
的副本,依此类推。这就是不可变对象如此有价值的原因,它们打破了“无限复制”链。
由于这个问题越来越受欢迎,我还应该提到 Brian Goetz's book, Java 并发实践, 这就是我了解这些的方式技术,我基本上是在解释上面那本书。
好吧,Java docs
提供了步骤A Strategy for Defining Immutable Objects
The following rules define a simple strategy for creating immutable objects. Not all classes documented as "immutable" follow these rules. This does not necessarily mean the creators of these classes were sloppy — they may have good reason for believing that instances of their classes never change after construction. However, such strategies require sophisticated analysis and are not for beginners.
- Don't provide "setter" methods — methods that modify fields or objects referred to by fields.
- Make all fields final and private.
- Don't allow subclasses to override methods. The simplest way to do this is to declare the class as final. A more sophisticated approach is to make the constructor private and construct instances in factory methods.
- If the instance fields include references to mutable objects, don't allow those objects to be changed:
- Don't provide methods that modify the mutable objects.
- Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.
地址 class 是可变的,因为您可以使用 setStreet 方法修改它。 所以其他class可以修改这个class.
我们可以通过在传入 Address 实例时复制它来防御这种情况,而不是信任对给定实例的引用。
使 Address 对象成为最终对象
private final Address address;
其次,
this.address = new Address(address.getStreet());
在地址 class 中创建构造函数,为街道设置 Street.Remove setter 方法。
最后代替
public Address getAddress(){
return address;
}
使用
public Address getAddress(){
return new Address(address.getStreet());
}
如果你想把一个可变对象封装成一个不可变对象,那么你需要:
创建可变对象的副本(即通过复制构造函数、克隆、serialization/deserialization等); 永远不要存储对原始可变对象的引用。
从不 return 可变对象。如果必须,则 return 对象的副本。
避免可以更改可变对象的方法。
public Employee(int id, Address address){ this.id = id; this.address=new Address(); this.address.setStreet( address.getStreet() ); } public Address getAddress() { Address nuAdd = new Address(); // must copy here too nuAdd.setStreet( address.getStreet() ); return nuAdd; }
你也可以通过克隆来使用浅拷贝
public final class Employee{
private final int id;
private Address address;
public Employee(int id, Address address)
{
this.id = id;
this.address=address.clone();
}
public int getId(){
return id;
}
public Address getAddress(){
return address.clone();
}
}
使用它会在 Employee class 中创建一个单独的 Address 对象,因此在这种情况下,对在 Employee 构造函数中作为参数传递的 Address 对象所做的任何更改都不会更改 Employee [= 的成员变量 Address 对象21=].
getAddress() 方法也返回一个克隆对象,因此对该方法获取的对象所做的任何更改都不会影响 Employee class 的地址对象。
注: 要使用此功能,请使地址 class 可克隆。
在 Employee class 中,return getAddress() 方法中地址实例的深度克隆副本,而不是 returning 原始实例。这样,如果有人更改了地址实例,它就无法反映原始实例。但有一个条件,Address class 必须实现 Cloneable 接口。
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() throws CloneNotSupportedException{
return (Address) address.clone();
}
}
What is difference between the creating the object of employee calling the clonne method.
1) 在构造函数和getter方法中创建员工对象并通过在构造函数中创建员工对象并将员工的必填字段设置到其中来设置所需的值,如下所示。
Example :
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;
}
2) cloning the employee object inside constructor and getter method as below.
Example :
public final class Employee{
private final int id;
private Address address;
public Employee(int id, Address address)
{
this.id = id;
this.address=address.clone();
}
public int getId(){
return id;
}
public Address getAddress(){
return address.clone();
}
}