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.