Java 我们如何使用可变引用保持类的不变性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34109363/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can we maintain Immutability of a class with a mutable reference
提问by user1111880
I know all the basic rules to make our class immutable but I am a little confused when there is another class reference. I know if there is collection instead of Address
then we can make use of Collections.unmodifiableList(new ArrayList<>(modifiable));
and then we can make our class immutable. But in below case I am still unable to get the concept.
我知道使我们的类不可变的所有基本规则,但是当有另一个类引用时我有点困惑。我知道如果有集合而不是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;
}
}
采纳答案by markspace
Well, the concept is reading the JLS and understanding it. In this case, the JLS says:
嗯,这个概念是阅读 JLS 并理解它。在这种情况下,JLS 说:
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.
final 字段还允许程序员在没有同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变的,即使使用数据竞争在线程之间传递对不可变对象的引用也是如此。这可以提供安全保证,防止错误或恶意代码滥用不可变类。必须正确使用 final 字段以提供不变性保证。
final 字段的使用模型很简单:在对象的构造函数中设置对象的 final 字段;并且不要在对象的构造函数完成之前在另一个线程可以看到它的地方写入对正在构造的对象的引用。如果遵循这一点,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。它还将看到那些最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样最新。
So you need to:
所以你需要:
- Make
address
both final and private. - For any mutable object, you must prevent the reference to that object from being seen externally.
- 使
address
两者都成为最终的和私有的。 - 对于任何可变对象,您必须防止从外部看到对该对象的引用。
In this case, #2 probably means you can't return a reference to Address like you have with getAddress()
. Andyou have to make a defensive copy in the constructor. I.e., make a copy of any mutable parameter, and store the copy in Employee. If you can't make a defensive copy, there's really no way to make Employee immutable.
在这种情况下,#2 可能意味着您不能像使用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;
}
Implementing clone()
or something similar (a copy ctor) would make creating defensive objects easier for complicated classes. However, the best recommendation I think would be to make Address
immutable. Once you do that you can freely pass around its reference without any thread-safety issues.
实现clone()
或类似的东西(复制构造函数)将使复杂类更容易创建防御对象。但是,我认为最好的建议是使Address
不可变。一旦你这样做了,你就可以自由地传递它的引用而没有任何线程安全问题。
In this example, notice I do NOThave to copy the value of street
. Street
is a String, and strings are immutable. If street
consisted of mutable fields (integer street number for example) then I wouldhave to make a copy of street
also, and so on ad infinitum. This is why immutable objects are so valuable, they break the "infinite copy" chain.
在这个例子中,通知我不是要复制的价值street
。 Street
是一个字符串,字符串是不可变的。如果street
由可变字段(例如整数街道号码)组成,那么我将不得不制作一个副本street
,以此类推。这就是为什么不可变对象如此有价值,它们打破了“无限复制”链。
回答by jonasnas
So in your example Employee
class is immutable, because once it is created, you can't change its state, because it has only getter methods.
所以在你的示例Employee
类中是不可变的,因为一旦它被创建,你就不能改变它的状态,因为它只有 getter 方法。
Address
class is mutable
because you can modify it with the setStreet
method.
Address
class 是mutable
因为您可以使用该setStreet
方法对其进行修改。
So if you have other class which uses Address
object, you are sure that that class can't modify the objects state.
因此,如果您有其他使用Address
对象的类,则可以确定该类无法修改对象状态。
回答by Naruto
Well there is steps provided by Java docs
好吧,Java 文档提供了步骤
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.
定义不可变对象的策略
以下规则定义了创建不可变对象的简单策略。并非所有记录为“不可变”的类都遵循这些规则。这并不一定意味着这些类的创建者马虎——他们可能有充分的理由相信他们的类的实例在构建后永远不会改变。但是,此类策略需要复杂的分析,不适合初学者。
- 不要提供“setter”方法——修改字段或字段引用的对象的方法。
- 将所有字段设为最终和私有。
- 不允许子类覆盖方法。最简单的方法是将类声明为 final。更复杂的方法是使构造函数私有并在工厂方法中构造实例。
- 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
- 不要提供修改可变对象的方法。
- 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
Address class is mutable because you can modify it with the setStreet method. So other class can modify this class.
Address 类是可变的,因为您可以使用 setStreet 方法修改它。所以其他类可以修改这个类。
We can defend against this by taking a copy of the of the Address instance when it is passed in rather than trusting the reference to the instance we are given.
我们可以通过在传入的 Address 实例的副本而不是信任对我们提供的实例的引用来防御这种情况。
Making Address object final
使地址对象成为最终对象
private final Address address;
Secondly,
其次,
this.address = new Address(address.getStreet());
Create constructor in Address class that sets Street.Remove setter method for street.
在为街道设置 Street.Remove setter 方法的 Address 类中创建构造函数。
And finally instead of
最后而不是
public Address getAddress(){
return address;
}
Use
用
public Address getAddress(){
return new Address(address.getStreet());
}
回答by Neeraj Gahlawat
If you want to encapsulate a mutable object into an immutable one, then you need to:
如果要将可变对象封装为不可变对象,则需要:
- Create a copy of the mutable object(i.e. via copy constructor, cloning, serialization/deserialization, etc.); never store the reference to the original mutable object.
- Never return the mutable object. If you must to, then return a copyof the object.
- Avoid methods which can change the mutable object.
- 创建可变对象的副本(即通过复制构造函数、克隆、序列化/反序列化等);永远不要存储对原始可变对象的引用。
- 永远不要返回可变对象。如果必须,则返回对象的副本。
- 避免可以改变可变对象的方法。
public Employee(int id, Address address){
公共员工(int id,地址地址){
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;
}
回答by sanjeev51
You can also use shallow copy using cloning
您还可以使用克隆来使用浅拷贝
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();
}
}
Using this will create a separate object of Address in Employee class so in this case any changes made to the Address object passed as argument in Employee constructor will not change the member variable Address object of Employee class.
使用这将在 Employee 类中创建一个单独的 Address 对象,因此在这种情况下,对作为 Employee 构造函数中的参数传递的 Address 对象所做的任何更改都不会更改 Employee 类的成员变量 Address 对象。
The getAddress() method is also returning a clone object so any changes made to the object fetched by this method do not effect the address object of Employee class.
getAddress() 方法还返回一个克隆对象,因此对该方法获取的对象所做的任何更改都不会影响 Employee 类的地址对象。
Note:To use this make Address class Cloneable.
注意:要使用它使地址类可克隆。
回答by Anil Nivargi
In Employee class, return the deep clone copy of the address instance in the getAddress() method instead of returning original instance. So that if anybody changed the address instance, it can not reflect the original instance. But one condition, Address class must implements Cloneable interface.
在 Employee 类中,在 getAddress() 方法中返回地址实例的深度克隆副本,而不是返回原始实例。这样如果有人更改了地址实例,它就不能反映原始实例。但有一个条件,Address 类必须实现 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();
}
}
Reference :-Immutable Class in java
参考:- java中的不可变类
回答by Raju Sharma
What is difference between the creating the object of employee calling the clonne method.
1) Creating employee object inside constructor and getter method and setting the required Value by creating the object of employee inside constructor and setting the required field of employee into it as below.
1)在构造函数和getter方法中创建employee对象并通过在构造函数中创建employee的对象并将employee的必填字段设置到其中来设置所需的值,如下所示。
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();
}
}