Modifying an Unmodifiable List

3 years ago by in Articles, Collections Tagged: , , , , ,

The Java Collections Framework provides an easy and simple way to create unmodifiable lists, sets and maps from existing ones, using the CollectionsunmodifiableXXX() methods (Java Doc). In this article we will discuss how this works and will demonstrate common pitfalls when using such methods.

All code listed below is available at: https://github.com/javacreed/modifying-an-unmodifiable-list, under the collections project. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.

Unmodifiable List

Consider the following simple example.

final List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("is");

final List<String> unmodifiable = Collections.unmodifiableList(modifiable);
System.out.println("Before modification: " + unmodifiable);

modifiable.add("the");
modifiable.add("best");

System.out.println("After modification: " + unmodifiable);

Here we have a list named: modifiable, from which we create an unmodifiable version using the Collections‘ method unmodifiableList(), named: unmodifiable. As their names imply, one list is modifiable (we can add and remove elements) while the other is read-only.

When executed, the above code will print the following to the command prompt.

Before modification: [Java, is]
After modification: [Java, is, the, best]

If this was a surprise or an unexpected answer, then you should continue reading this article to find out why this was produced.

Why was the list unmodifiable modified?

Let us first have a look at the Java Doc of the CollectionsunmodifiableList() method.

public static <T> List<T> unmodifiableList(List<? extends T> list)

Returns an unmodifiable view of the specified list. This method allows modules to provide users with “read-only” access to internal lists. Query operations on the returned list “read through” to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an UnsupportedOperationException.

The returned list will be serializable if the specified list is serializable. Similarly, the returned list will implement RandomAccess if the specified list does.

Parameters:
list – the list for which an unmodifiable view is to be returned.

Returns:
an unmodifiable view of the specified list.

The documentation mentions that the returned object (the unmodifiable list, referred to as returned list in this paragraph) is a read-only view of the given one (referred to in this paragraph as the given list). It says nothing about the behaviour we just saw. Well now we know that while we cannot modify the returned list, any changes to the given list are observed by the returned one.

This is very important and very useful too. We can share a list with another object without the object (receiver) being able to modify it. But this is not always desirable as we can have weird surprises, especially when it comes to builders and immutable objects, as discussed in the following section.

Builders and Immutable objects

This problem was first observed during code review of an immutable object that makes use of the builder pattern. The object that was planned to be immutable, was not truly immutable as we could still modify it past its creation.

Consider the following class.

package com.javacreed.examples.collections.part2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Person {

  public static class Builder {
    private String name;
    private final List<String> friends = new ArrayList<>();

    public Builder addFriend(final String name) {
      this.friends.add(name);
      return this;
    }

    public Person build() {
      return new Person(this);
    }

    public Builder setName(final String name) {
      this.name = name;
      return this;
    }
  }

  private final String name;
  private final List<String> friends;

  private Person(final Builder builder) {
    this.name = builder.name;
    this.friends = Collections.unmodifiableList(builder.friends);
  }

  public List<String> getFriends() {
    return friends;
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return String.format("%s has %d friends", name, friends.size());
  }
}

Here we have a class that represents a person. The person has a list of friends (which are of type String to keep this example as simple as possible). The Person class is expected to be immutable and therefore we cannot modify it once created. The Person class provides no public means by which another class can modify its state.

If observed closely, we will notice that this class is not truly immutable as owe can still modify it, even after it is created. This can be achieved through the Builder instance used to create the class as shown next. This class has the same weakness, if you may, as we saw in the previous example.

final Person.Builder builder = new Person.Builder();
builder.setName("Albert Attard");
builder.addFriend("John White");
builder.addFriend("Mary Vella");

final Person person = builder.build();
System.out.println("Before modification: " + person);

// Adding a new friend after the object was created
builder.addFriend("Joe Borg");
System.out.println("After modification: " + person);

The above code will print the following to the command prompt.

Before modification: Albert Attard has 2 friends
After modification: Albert Attard has 3 friends

This should be of no surprise as the Builder class is sharing the list of friends with the Person. In the following section we will see how we can prevent this behaviour from happening.

How to prevent modifications to the unmodifiable list?

The solution to this problem is quite simple and is highlighted in the following code.

final List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("is");

// Here we are creating a new array list
final List<String> unmodifiable = Collections.unmodifiableList(new ArrayList<>(modifiable));
System.out.println("Before modification: " + unmodifiable);

modifiable.add("the");
modifiable.add("best");

System.out.println("After modification: " + unmodifiable);

Note that in this example, we are not passing the modifiable list object, but a new list created from this one. When we create a list like this, the elements of the given list are simply copied to the one being created. Therefore, these two lists (the modifiable list and the one just created: new ArrayList<>(modifiable) list) are disconnected and they only share the elements. Any modifications to each list will not affect the other.

One has to be aware that if we modify any of the elements within any of the lists, then all variables referring to the modified object will observe the modification. This is not the case here as String is an immutable object and thus cannot be changed once created. But if the objects contained by these two lists where mutable, then any changes to the objects from one of the list will be observed in the peer element in the other list.

The above code will produce the following result

Before modification: [Java, is]
After modification: [Java, is]

Now the unmodifiable list was not affected by the changes made to the modifiable lost. The same technique can be applied to the Person class saw earlier to prevent the Builder instance from accidentally modifying the supposedly immutable object, as shown in the next example.

package com.javacreed.examples.collections.part3;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Person {

  public static class Builder {
    private String name;
    private final List<String> friends = new ArrayList<>();

    public Builder addFriend(final String name) {
      this.friends.add(name);
      return this;
    }

    public Person build() {
      return new Person(this);
    }

    public Builder setName(final String name) {
      this.name = name;
      return this;
    }
  }

  private final String name;
  private final List<String> friends;

  private Person(final Builder builder) {
    this.name = builder.name;
    this.friends = Collections.unmodifiableList(new ArrayList<>(builder.friends));
  }

  public List<String> getFriends() {
    return friends;
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return String.format("%s has %d friends", name, friends.size());
  }
}

This concludes our article about how to control whether an unmodifiable list is updated after its creation or not. Please note that the Collections‘ methods are working well as these were designed to work. It allows us to share a list, set or map with another objects without giving them the possibility to modify them, while we can manipulate them as required. Documentation should state this behaviour clearly as many who mistakenly believe that the returned list will not be affected by changes made to the given list.

Albert Attard

Albert Attard is a Java passionate and technical lead at a research group. You can find him on . Over the past years Albert worked on various Java projects including traditional server/client applications, modular applications, large data handling applications and concurrent data manipulation applications to name a few. He has a BSc degree from the University of London (Homepage) and an MSc Information Security with the same university. His MSc thesis (Book) received the 2012 SearchSecurity.co.UK award (Website).

5 Responses to “Modifying an Unmodifiable List”


bhupesh
August 18, 2013 Reply

Nice way to demonstrate the Concept.

Anonymous
December 20, 2013 Reply

Great post.

Anon
February 7, 2014 Reply

well explained

anonymous
March 6, 2014 Reply

good

Avinash Solanki
September 10, 2014 Reply

great explanation of the concept ..

Leave a Comment