在Java中,也可能在其他语言中,例如在getter中,您必须决定是否要返回对某事物或克隆(副本)的引用。
return myInstance;
只是返回引用非常快,不需要额外的内存,但实例的修改得到了回写"原来的。
return myInstance.clone();
返回克隆需要时间,并为该变量加倍内存,但要保证其安全。
可以在某些内容上创建不可变的视图:
return MyUtil.immutableView(myInstance);
但有时我希望修改它,只是不要将其写回来。
现在我的想法是,是否有可能(或者已经完成,或者是否有编程语言来创建它)来创建一个对象,该对象最初是对的引用,只要没有修改。第一次修改开始后,引用会自行更新为克隆。
这样的事情:
Class<T> CloneReference
{
T ref;
boolean cloned=false;
public CloneReference(T ref) {this.ref=ref;}
T getForReadOnly()
{
return ref;
}
T getForReadWrite()
{
if(!cloned) ref=ref.clone();
return ref;
}
}
不幸的是,这个解决方案很复杂,笨拙且易于破解(调用getForReadOnly()然后使用实例更改操作)。有可能做得更好,或者只是用Java不可能吗?
答案 0 :(得分:2)
您正在寻找的内容与Copy-On-Write非常相似。我记得PHP是一种实现此目的的语言。
我认为基本上也可以在Java中实现COW。我想回到一些用原始实例初始化的proxy。在第一次写访问时,代理将继续使用副本。这是SSCCE:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.junit.Test;
import static org.junit.Assert.*;
public class CowSSCCE {
public interface Bean {
public String getName();
public void setName(String name);
public Object clone();
}
public class BeanImpl implements Bean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object clone() {
BeanImpl copy = new BeanImpl();
copy.name = new String(name);
return copy;
}
}
public class COWInvocationHandler implements InvocationHandler {
private Bean instance;
private boolean copy = false;
public COWInvocationHandler(Bean instance) {
this.instance = instance;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// copy only on the first setter call.
if (!copy && method.getName().startsWith("set")) {
instance = (Bean) instance.clone();
copy = true;
}
return method.invoke(instance, args);
}
}
@Test
public void testCOW() {
Bean original = new BeanImpl();
original.setName("original");
Bean reference = (Bean) Proxy.newProxyInstance(
Bean.class.getClassLoader(), new Class[] { Bean.class },
new COWInvocationHandler(original));
// no write access, reference is pointing to the original instance
assertEquals(original.getName(), reference.getName());
assertEquals(original.toString(), reference.toString());
// write access, reference is a copied instance
reference.setName("reference");
assertEquals("reference", reference.getName());
assertNotEquals(original.getName(), reference.getName());
assertNotEquals(original.toString(), reference.toString());
}
}
正如某人提到的可读性,这不应成为一个问题:写一个建议,例如:注释@ReturnCopyOnwriteReference
,它使用代理透明地替换返回的对象。返回此类代理的API方法只需要该注释:
@ReturnCopyOnwriteReference
public Bean getExpensiveBean() {
return originalBean;
}
如果您只是在寻找COW集合,请使用Java CopyOnWriteArrayList
。
答案 1 :(得分:1)
看看Scala编程语言。它在JVM中运行,大多数情况下的变量都是不可变的。
在Java中有一个java.util.Collections#unmodifiableCollection()方法,它将任何集合包装到不可修改的集合中。这可以防止编辑。但是我没有看到或想到任何可以提供所需行为的用例。
答案 2 :(得分:0)
听起来你想要像C ++这样的const正确性。不幸的是,在Java中没有什么天生的东西,但有几种策略可以实现类似的结果。
其中任何一点的重点是确保程序的正确性,并有助于减少副作用。
始终返回副本,这样就可以安全地修改类中的对象。实现复制构造函数可能是最简单的,但您可能需要深层复制,这意味着任何非原始成员都需要提供一种获取自身深层副本的方法(如另一个复制构造函数)。
Java的Collections
类使用Collections.unmodifiableList
等执行此操作。此方法接受List
并使用它自己的代理(私有){{1将调用转发给访问器方法的实现,但mutator方法抛出List
。这有点危险,因为你只能通过文档来支持它。
您可以始终拥有一个不可修改的顶级UnsupportedOpperationException
和一个interface Foo
,必要时您只返回前者。可能是最好的选择,因为您可以使用编译器强制执行可变性而不是运行时异常,如上所述。
答案 3 :(得分:0)
我曾在博客中写过这个主题:
http://eyalsch.wordpress.com/2010/02/11/refdamage/
一般来说,我尝试遵循以下原则,关于“主”对象和“逃避”它的引用(作为参数或返回值):
1)如果主对象公开了一些引用,我们必须确保不能以使类处于不一致状态的方式操作引用。这可以通过多种方式完成(防御性副本,不变性,听众等)。
2)如果对引用状态的修改是合法的并且自动反映在主对象状态中,则必须正确记录。
3)如果调用者希望在不影响主对象的情况下更新引用状态,则调用者有责任正确克隆。