Or: why is my list still mutable?
Today I learnt a new thing about Collections.unmodifiableList.
First, I noted that Sonar wasn’t raising a violation where I thought it should.
Take the sample class below
public final class MyImmutableClass {
private final List<String> myList;
public MyImmutableClass(List<String>myList) {
this.myList = myList;
}
public List<String> getMyList() {
return myList;
}
}
I would expect sonar to raise the following:
Malicious code vulnerability - May expose internal representation by incorporating reference to mutable object (Findbugs: EI_EXPOSE_REP2) Malicious code vulnerability - May expose internal representation by returning reference to mutable object (Findbugs: EI_EXPOSE_REP)
But it didn’t. This is a mystery in itself, as I am sure it has picked me up on this before.
Anyway, no problem, I wrote a test for the class myself:
/**
* Test of getMyList method, of class MyImmutableClass.
*/
@Test
public void testGetMyList() {
List<String> list = new ArrayList<String>();
list.add("Potato");
list.add("Apple");
MyImmutableClass instance = new MyImmutableClass(list);
assertEquals(2,instance.getMyList().size());
//check for immutability
list.add("item 3");
assertEquals(2,instance.getMyList().size());
try {
instance.getMyList().add("Message3");
fail("Should not be possible");
} catch (Exception ex) {
assertTrue( ex instanceof UnsupportedOperationException);
}
}
Of course, the test fails with the code as it is.
So I modified it to this:
public final class MyImmutableClass {
private final List<String> myList;
public MyImmutableClass(List<String>myList) {
this.myList = Collections.unmodifiableList(myList);
}
public List<String> getMyList() {
return Collections.unmodifiableList(myList);
}
}
And it STILL failed. I was very confused. How come Collections.unmodifiableList in the constructer wasn’t stopping the list inside the class from changing?
It took some googling to find the answer. For example: this stackoverflow post.
If you pay proper attention to the javadoc for Collections.unmodifiableList, it makes sense.
* 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.
So, this just wraps the original list inside an unmodifiable view. I can’t modify the one inside my class, but if I modify the one I used to create the class, the view reflects the update.
The correct way to make my class immutable is:
public final class MyImmutableClass {
private final List<String> myList;
public MyImmutableClass(List<String>myList) {
this.myList = Collections.unmodifiableList(new ArrayList<String>(myList));
}
public List<String> getMyList() {
return Collections.unmodifiableList(myList);
}
}
Phew. As an afterthought, because sonar and findbugs did not pick this up, I’m thinking of taking a look at this: http://mutability-detector.blogspot.co.uk/. We like to make all classes immutable, unless there is a good reason not to. It would be interesting to see what else has slipped through.