结合克隆和参考的优势?

时间:2014-03-18 13:43:03

标签: java clone

在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不可能吗?

4 个答案:

答案 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)如果调用者希望在不影响主对象的情况下更新引用状态,则调用者有责任正确克隆。