Question

In Java, and maybe also in other languages, for example in a getter you have to decide if you want to return a reference to something or a clone (copy).

return myInstance; 

Just returning the reference is very fast and needs no additional memory but modifications of an instance get "written back" the the original one.

return myInstance.clone();

Returning a clone needs time and doubles the memory for that variable but keeps it safe.

It is possible to create an immutable view on something:

return MyUtil.immutableView(myInstance);

but then sometimes I want to modify it, just not to have it written back.

Now my idea is, is it possible (or already done, or is there a programming language that does it) to create an object that is initially a reference to something as long as there are no modifications. As soon as the first modification begins, the reference would update itself to a clone.

Something like this:

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;
 }

}

Unfortunately, this solution is complicated, clumsy and easy to break (calling getForReadOnly() and then using instance changing operations). Is it possible to do better or is that just not possible with Java?

Was it helpful?

Solution

What you're looking for sounds pretty much like Copy-On-Write. I remember that PHP is a language which did implement this.

I think it should basically be possible to implement COW in Java as well. I think of returning some proxy which is initialized with the original instance. On the first write access the proxy will continue using a copy. Here's a 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());
    }

}

As someone mentioned readability, this shouldn't be an issue: Write an advice for e.g. the annotation @ReturnCopyOnwriteReference which replaces transparently the returned object with the proxy. An API method which returns such a proxy needs only that annotation:

@ReturnCopyOnwriteReference
public Bean getExpensiveBean() {
    return originalBean;
}

If you're just looking for a COW collection use Java's CopyOnWriteArrayList.

OTHER TIPS

Look at Scala programming language. It runs in JVM, and variables in most cases are immutable.

In Java there is a java.util.Collections#unmodifiableCollection() method, which wraps any collection into unmodifiable collection. This prevents it from editing. But I did not saw or think of any use case which would provide your desired behavior.

It sounds like you want something like C++'s const correctness. Unfortunately, there's nothing so innate in Java, but there are several strategies for achieving a similar result.

The whole point of any of these is to insure program correctness, and helping to reduce side effects.

Copy constructor

Always return a copy, that way the object inside the class is safe from modification. Implementing a copy constructor is probably the easiest, but you probably want a deep copy, which means any non-primitive members need to provide a way to obtain a deep copy of themselves (like another copy constructor).

Unmodifiable views

Java's Collections class does this with Collections.unmodifiableList, etc. This method accepts a List and proxies it with it's own (private) List implementation that forwards calls to accessor methods, but mutator methods throw an UnsupportedOpperationException. This is a little more dangerous because you can only support this with documentation.

Class hierarchy

You could always have a top level interface Foo which is unmodifiable, and an interface ModifiableFoo, where necessary you only return the former. Probably the best option since you can enforce mutability with the compiler and not runtime exceptions, as above.

I wrote about this subject once in my blog:

http://eyalsch.wordpress.com/2010/02/11/refdamage/

In general, I try to follow the following principles, with respect to the "main" object and the reference that "escapes" from it (either as a parameter or a returned value):

1) If the main object exposes some reference, we must make sure that the reference can't be manipulated in such a way that the class is left in an inconsistent state. This can be done in many ways (defensive copies, immutability, listeners, etc..).

2) In case that modifications to the reference's state are legal and are automatically reflected in the main object state, this must be properly documented.

3) If the caller wishes to update the reference state without affecting the main object, it's the caller's responsibility to clone properly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top