Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable vs Unmodifiable collection

An unmodifiable collection is often a wrapper around a modifiable collection which other code may still have access to. So while you can't make any changes to it if you only have a reference to the unmodifiable collection, you can't rely on the contents not changing.

An immutable collection guarantees that nothing can change the collection any more. If it wraps a modifiable collection, it makes sure that no other code has access to that modifiable collection. Note that although no code can change which objects the collection contains references to, the objects themselves may still be mutable - creating an immutable collection of StringBuilder doesn't somehow "freeze" those objects.

Basically, the difference is about whether other code may be able to change the collection behind your back.


Basically unModifiable Collection is a view, So indirectly it could still be 'modified' from some other reference that is modifiable. Also as its just a readonly view of annother collection , When the source collection changes unModifiable Collection will always present with latest values.

However immutable Collection can be treated as a readonly copy of another collection and can not be modified. In this case when the source collection changes , immutable Collection do not reflect the changes

Here is a testcase to visualise this difference.

@Test
public void testList() {

    List<String> modifiableList = new ArrayList<String>();
    modifiableList.add("a");

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("--");


    //unModifiableList

    assertEquals(1, modifiableList.size());

    List<String> unModifiableList=Collections.unmodifiableList(
                                        modifiableList);

    modifiableList.add("b");

    boolean exceptionThrown=false;
    try {
        unModifiableList.add("b");
        fail("add supported for unModifiableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("unModifiableList.add() not supported");
    }
    assertTrue(exceptionThrown);

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);

    assertEquals(2, modifiableList.size());
    assertEquals(2, unModifiableList.size());
            System.out.println("--");



            //immutableList


    List<String> immutableList=Collections.unmodifiableList(
                            new ArrayList<String>(modifiableList));

    modifiableList.add("c");

    exceptionThrown=false;
    try {
        immutableList.add("c");
        fail("add supported for immutableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("immutableList.add() not supported");
    }
    assertTrue(exceptionThrown);


    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);
    System.out.println("immutableList:"+immutableList);
    System.out.println("--");

    assertEquals(3, modifiableList.size());
    assertEquals(3, unModifiableList.size());
    assertEquals(2, immutableList.size());

}

Output

modifiableList:[a]
--
unModifiableList.add() not supported
modifiableList:[a, b]
unModifiableList:[a, b]
--
immutableList.add() not supported
modifiableList:[a, b, c]
unModifiableList:[a, b, c]
immutableList:[a, b]
--

I think the main difference is that the owner of a mutable collection might want to provide access to the collection to some other code, but provide that access through an interface that doens't allow the other code to modify the collection (while reserving that capability to the owning code). So the collection isn't immutable, but certain users aren't permitted to change the collection.

Oracle's Java Collection Wrapper tutorial has this to say (emphasis added):

Unmodifiable wrappers have two main uses, as follows:

  • To make a collection immutable once it has been built. In this case, it's good practice not to maintain a reference to the backing collection. This absolutely guarantees immutability.
  • To allow certain clients read-only access to your data structures. You keep a reference to the backing collection but hand out a reference to the wrapper. In this way, clients can look but not modify, while you maintain full access.

To quote The Java™ Tutorials:

Unlike synchronization wrappers, which add functionality to the wrapped collection, the unmodifiable wrappers take functionality away. In particular, they take away the ability to modify the collection by intercepting all the operations that would modify the collection and throwing an UnsupportedOperationException. Unmodifiable wrappers have two main uses, as follows:

  • To make a collection immutable once it has been built. In this case, it's good practice not to maintain a reference to the backing collection. This absolutely guarantees immutability.

  • To allow certain clients read-only access to your data structures. You keep a reference to the backing collection but hand out a reference to the wrapper. In this way, clients can look but not modify, while you maintain full access.

(emphasis mine)

This really sums it up.


If we are talking about JDK Unmodifiable* vs guava Immutable*, actually the difference is also in performance. Immutable collections can be both faster and more memory-efficient if they are not wrappers around regular collections (JDK implementations are wrappers). Citing the guava team:

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

<...>

  • inefficient: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.

    // normal list
    List list1 = new ArrayList();
    list1.add(1);

    // unmodifiable list
    List list2 = Collections.unmodifiableList(list1);

    // immutable list
    List list3 = Collections.unmodifiableList(new ArrayList<>(list1));

    list1.add(2);
    list1.add(3);

    System.out.println(list1);
    System.out.println(list2);
    System.out.println(list3);

Output:

[1, 2, 3]
[1, 2, 3]
[1]