Suppose I have to following set:
Set<String> fruits = new HashSet<String>()
fruits.add("Apple")
fruits.add("Grapes")
fruits.add("Orange")
Set<String> unmodifiableFruits = Collections.unmodifiableSet(new HashSet<String>(fruits))
unmodifiableFruits.add("Peach") // -- Throws UnsupportedOperationException
Set<String> fruitSet = Collections.unmodifiableCollection(fruits)
fruitSet.add("Peach")
println(fruitSet)
If I were to use Collections.unmodifiableSet() it throws an exception when I attempt to use the add() method, but that isn't the case for Collections.unmodifiableCollection(). Why?
According the the documentation it should throw an error:
Returns an unmodifiable view of the specified collection. This method allows modules to provide users with "read-only" access to internal collections. Query operations on the returned collection "read through" to the specified collection, and attempts to modify the returned collection, whether direct or via its iterator, result in an UnsupportedOperationException.
All the code is written using Groovy 2.5.2
Short answer: adding
Peachto this collection is possible, because Groovy does dynamic cast fromCollectiontoSettype, sofruitSetvariable is not of typeCollections$UnmodifiableCollectionbutLinkedHashSet.Take a look at this simple exemplary class:
In statically compiled language like Java, following line would throw compilation error:
This is because
Collectioncannot be cast toSet(it works in opposite direction, becauseSetextendsCollection). Now, because Groovy is a dynamic language by design, it tries to cast to the type on the left hand side if the type returned on the right hand side is not accessible for the type on the left side. If you compile this code do a.classfile and you decompile it, you will see something like this:The interesting line is the following one:
Groovy sees that you have specified a type of
fruitSetasSet<String>and because right side expression returns aCollection, it tries to cast it to the desired type. Now, if we track what happens next we will find out thatScriptBytecodeAdapter.castToType()goes to:And this is why
fruitSetis aLinkedHashSetand notCollections$UnmodifableCollection.Of course it works just fine for
Collections.unmodifiableSet(fruits), because in this case there is no cast needed -Collections$UnmodifiableSetimplementsSetso there is no dynamic casting involved.How to prevent similar situations?
If you don't need any Groovy dynamic features, use static compilation to avoid problems with Groovy's dynamic nature. If we modify this example just by adding
@CompileStaticannotation over the class, it would not compile and we would be early warned:Secondly, always use valid types. If the method returns
Collection, assign it toCollection. You can play around with dynamic casts in runtime, but you have to be aware of consequences it may have.Hope it helps.