8

I would like to feel the 'magic power' of the copyOf() method of Guava guava-libraries.

There is small app that I use to check it.

Here is the documentation:

The JDK provides Collections.unmodifiableXXX methods, but in our opinion, these can be

  • unwieldy and verbose; unpleasant to use everywhere you want to make defensive copies
  • unsafe: the returned collections are only truly immutable if nobody holds a reference to the original collection

So, I try to build a model where "someone holds a reference to the original collection". Thus, working with a copy of collection I should not be worried about changing value on copy. But magic does not work so far (there are two tries: 1. copyOf(collection), 2. copyOf(iterator)):

import com.google.common.collect.ImmutableList;

import java.util.LinkedList;
import java.util.List;

class MyObject {    
    String name;    
    public MyObject(String name) {this.name = name;}    
    @Override
    public String toString() {
      return name;
    }    
}

public class ListUnsafe {

    List<MyObject> list = new LinkedList<MyObject>();    
    {
        list.add(new MyObject("a"));
        list.add(new MyObject("b"));
        list.add(new MyObject("c"));
    }

    public List<MyObject> getList() {
        return ImmutableList.copyOf(list);
    }

    public List<MyObject> getCopyIterator() {
        return ImmutableList.copyOf(list.iterator());
    }

    public static void main(String[] args) {

        ListUnsafe obj = new ListUnsafe();
        {
           MyObject ref = obj.list.get(0);

           List<MyObject> myList =  obj.getList();

           MyObject copyObj = myList.get(0);
           copyObj.name = "new";

           System.out.println("ref: " + ref);
        }

        obj = new ListUnsafe();
        {
            MyObject ref = obj.list.get(0);

            List<MyObject> myList =  obj.getCopyIterator();

            MyObject copyObj = myList.iterator().next();

            copyObj.name = "new";

            System.out.println("ref: " + ref);

        }

    }

}

The output:

ref: new
ref: new

It means that we changed original data. What we did not want.

Question

Why it does not do copy?
How it differs from unmodifiableXXX?

There is link to similar question:

The answer says about copyOf:

  • (from source) copyOf(Collection) instance doesn't create temporary ArrayList (copyOf(Iterable) and copyOf(Iterator) do so).
Community
  • 1
  • 1
ses
  • 13,174
  • 31
  • 123
  • 226
  • 1
    Presumably the list itself is immutable; the contained elements aren't. – Oliver Charlesworth May 27 '13 at 01:33
  • 2
    Note: the Collections.unmodifiable*() methods are not intended to return an immutable instance - they are intended to return a view of the collection that cannot be modified through that view. This can be valuable when the provider of the data needs to control modifications. – Andy Thomas May 27 '13 at 02:27
  • They compare guava to jdk's unmodifiableXXX. Is if it could resolve issue if someone "holds a reference to the original collection" and this ref would hold old unchangeable value. As I understand it. How it differs from unmodifiableXXX? – ses May 27 '13 at 03:26
  • 1
    these copy is shallow copy. i.e. The list itself is copied, but they are still pointing to same objects. If you need deep copy you need to implement yourself – Adrian Shum May 27 '13 at 03:27

2 Answers2

21
  1. ImmutableList does not magically make the elements immutable; it's the list that cannot be modified, not the elements it contains.
  2. ImmutableList.copyOf makes a copy unless it is copying a list that is already an ImmutableList. If you call ImmutableList.copyOf(list) twice for the same immutable list, you will get two different copies.
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • 1
    yeah.. I've already check sources. Probably this is the best thing to do first. – ses May 28 '13 at 01:37
5

First of all thank you for all answers. I came up win an example that satisfies me. That shows difference between ImmutableSet.copyOf(..) and JDK's Collections.unmodifiableSet(..);

And yes: it does shallow copy (otherwise it was strange, because it was magic).

class Person {
    public Person(String name) {this.name = name;}
    public Person(String name, Person relation) {this(name);this.relation = relation;}

    String name;
    Person relation;
}

public class ImmutableExample {
    public static void main(String[] args) {

        Person bob = new Person("bob");
        Person chris = new Person("chris", bob);
        Person nullPerson = null; // NULL!

        final Set<Person> originalSet = new LinkedHashSet<Person>(Arrays.asList(
                bob,
                chris
                // nullPerson // NULL !  <- if we use null then we can not convert it to ImmutableSet
               ));

        Set<Person> googleSet = ImmutableSet.copyOf(originalSet);

        Set<Person> javaSet = Collections.unmodifiableSet(originalSet);

        // is it SAFE to delete someone from original collection?
        originalSet.remove(chris);

        // google
        for (Person person : googleSet) System.out.println(person.name); // Chris is still here! And this is good! Stay with us, Chris!

        // java standard
        for (Person person : javaSet) System.out.println(person.name); // Where is Chris ??

        //newSet.add(new Person("newGuy"));  // UnsupportedOperationException

    }
}

(The purpose of original question, though was providing client absolutely safe list for reading. But this could be achieved only with manual cloning of all objects in the list [if they are not immutable]. To provide something that even better (in terms of immutability and concurrency safety) than CopyOnWriteArrayList. I mean: it provides safe iterator, but not data itself if they are mutable in the returning list - client still can change data using references to returned (by getter) data items)

Community
  • 1
  • 1
ses
  • 13,174
  • 31
  • 123
  • 226