英文:
How to implement immutable and read-only versions of mutable objects effectively?
问题
Context:
* package data, public:
public interface _Data {
public String getData();
}
public class _PackageAPI {
DataHolder holder;
public void createHolder(String data) {
holder = new DataHolder();
holder.setData(data);
}
public void mutateHolder(String data) {
holder.setData(data);
}
public _Data getSnapshot() {
return DataSnapshot.from(holder.getData());
}
public _Data getReader() {
return holder.readOnly();
}
}
* package data, package-private:
class DataHolder {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public _Data readOnly() {
return new _Data() {
@Override
public String getData() {
return DataHolder.this.data;
}
};
}
}
class DataSnapshot {
public static _Data from(String data){
return new _Data() {
@Override
public String getData() {
return data;
}
};
}
}
* sample client usage:
package clientPackage;
import data._Data;
import data._PackageAPI;
public class ExampleRunner {
public static void main(String[] args) {
_PackageAPI handler;
System.out.println("Creating...");
handler = new _PackageAPI();
handler.createHolder("INITIAL DATA");
System.out.println("Done creating...");
_Data initialSnapShot = handler.getSnapshot();
_Data initialReader = handler.getReader();
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
System.out.println("initialReader class :" + initialReader.getClass() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("Mutating...");
handler.mutateHolder("MUTATED DATA");
_Data subsequentSnapShot = handler.getSnapshot();
_Data subsequentReader = handler.getReader();
System.out.println("Done mutating...");
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
System.out.println("subsequentReader holds :" + subsequentReader.getData() );
}
}
* And console output:
Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA
* FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?
* FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?
(Of course solution to this is to clone the String being referenced.)
* SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?
* THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?
EDIT : I would have put a paranoid tag if there was one
英文:
Context :
- package data, public :
public interface _Data {
public String getData();
}
public class _PackageAPI {
DataHolder holder;
public void createHolder(String data) {
holder = new DataHolder();
holder.setData(data);
}
public void mutateHolder(String data) {
holder.setData(data);
}
public _Data getSnapshot() {
return DataSnapshot.from(holder.getData());
}
public _Data getReader() {
return holder.readOnly();
}
}
- package data, package-private :
class DataHolder {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public _Data readOnly() {
return new _Data() {
@Override
public String getData() {
return DataHolder.this.data;
}
};
}
}
class DataSnapshot {
public static _Data from(String data){
return new _Data() {
@Override
public String getData() {
return data;
}
};
}
}
- sample client usage :
package clientPackage;
import data._Data;
import data._PackageAPI;
public class ExampleRunner {
public static void main(String[] args) {
_PackageAPI handler;
System.out.println("Creating...");
handler = new _PackageAPI();
handler.createHolder("INITIAL DATA");
System.out.println("Done creating...");
_Data initialSnapShot = handler.getSnapshot();
_Data initialReader = handler.getReader();
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
System.out.println("initialReader class :" + initialReader.getClass() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("Mutating...");
handler.mutateHolder("MUTATED DATA");
_Data subsequentSnapShot = handler.getSnapshot();
_Data subsequentReader = handler.getReader();
System.out.println("Done mutating...");
System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
System.out.println("initialReader holds :" + initialReader.getData() );
System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
System.out.println("subsequentReader holds :" + subsequentReader.getData() );
}
}
- And console output:
Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA
-
FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?
-
FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?
(Of course solution to this is to clone the String being referenced.)
-
SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?
-
THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?
EDIT : I would have put a paranoïd tag if there was one
答案1
得分: 0
以下是翻译好的内容:
> 是否安全,或者是否有可能通过访问该引用来突变 DataHolder
?如果是,如何操作?
由于 getData
返回的是一个不可变的 String
,调用者无法通过返回的引用来进行任何更改。您也无法通过 String
来访问 DataHolder
。为什么 String
会知道您的 DataHolder
类呢?
> 有没有办法突变 DataSnapshot$1
?
没有,因为它是一个只有一个方法的匿名内部类,该方法返回一个参数。参数是按值传递,因此您不必担心调用者会在另一侧更改其值。它还是一个 String
,这意味着调用者也不会对对象进行突变。
您可能之所以问这个问题,是因为您看到了 initialReader
是如何改变的。嗯,由于 DataHolder$1
是可变的 DataHolder
的内部类,即使它没有任何改变器方法,它实际上并不是不可变的。
> 是否安全暴露这样一个 DataHolder$1
,或者是否有办法通过 DataHolder$1
对象操纵 DataHolder
实例?
无法从内部类访问外部类,因此由于 DataHolder$1
中没有改变器方法,您无法从外部突变 DataHolder
,仅通过 DataHolder$1
也无法。
然而,如果 DataHolder
发生变化,这些变化将会反映在 DataHolder$1
上(正如您的示例代码所示),我认为这违背了不可变性的目的。
<hr>
以下是我在这种情况下如何实现不可变性。
我会让 DataHolder
实现 _Data
。DataHolder
当然可以做到这一点,不是吗?毕竟它有一个 String getData()
方法!DataHolder
将会有一个 asReadOnly
方法,该方法创建一个 this
的副本并返回 _Data
。事实上,我会将 _Data
重命名为 ReadOnlyData
。
英文:
> is this safe or is it somehow possible to mutate DataHolder
leveraging access to this reference? If yes, how ?
Since getData
returns a String
, which is immutable, the caller can't change anything through the returned reference. You can't access a DataHolder
through a String
either. Why would String
know about your DataHolder
class?
> is there anyway to mutate a DataSnapshot$1
?
No, because it is an anonymous inner class that only has one method that returns a parameter. Parameters are passed by value, so you don't need to worry about callers changing their values on the other side. It's also a String
, which means the callers won't be mutating the object either.
You might be asking this because you saw how initialReader
has changed. Well, since DataHolder$1
is an inner class of the mutable DataHolder
, it's not really immutable even if it doesn't have any mutator methods.
> is it safe to expose such a DataHolder$1
, or is there anyway to mess with the DataHolder
instance from the DataHolder$1
object ?
There is no way to access the outer class from the inner class, so since there are no mutator methods in DataHolder$1
, you can't mutate DataHolder
from the outside, with only a DataHolder$1
.
However, if DataHolder
changes, the changes will reflect on DataHolder$1
(as shown in your sample code), which I think defeats the purpose of immutability.
<hr>
Here's how I would implement immutability in this scenario.
I would make DataHolder
implement _Data
. DataHolder
certainly can do this, can't it? It has a String getData()
method after all! DataHolder
would have an asReadOnly
method that creates a copy of this
and returns _Data
. In fact, I'd rename _Data
to ReadOnlyData
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论