Search code examples
javagroovycollectionssetunmodifiable

Collections.unmodifiableCollection and Collections.unmodifiableSet


Suppose I have the following Set

Set<String> fruits = new HashSet<String>();
fruits.add("Apple")
fruits.add("Grapes")
fruits.add("Orange")

If I wanted to create a defensive copy, so that if the original list is modified, the copy doesn't reflect it, I can do this:

Set<String> unmodifiableFruits = Collections.unmodifiableSet(new HashSet(fruits))

so if I do this:

fruits.add("Pineapple")     
println(unmodifiableFruits)

unmodifiableFruits won't have pineapple.

or I can so this:

Set<String> unmodifiableFruits = Collections.unmodifiableCollection(fruits)

and the result is the same, unmodifiableFruits won't have pineapple.

Questions:

  1. Suppose if I were passing fruits as an argument to a class, is the preferred method the Collections.unmodifiableCollection()?

The reason is, I've read that declaring new in the constructor is a bad practice, if I were to use Collections.unmodifiableSet(), I would need to declare a new HashSet<String>(fruits).

  1. Why can't I do this ?

    Collections.unmodifiableSet(fruits)

and have it return an unmodifiable collection.

instead I have to do this:

Collections.unmodifiableSet(new HashSet<String>(fruits))

Is it because Set is an interface and it doesn't know which implementation to return?


Solution

  • Groovy has enhanced collection methods, meaning that it has added methods to the standard collection classes.

    Once of those methods is toSet():

    Convert a Collection to a Set. Always returns a new Set even if the Collection is already a Set.

    Example usage:

    def result = [1, 2, 2, 2, 3].toSet()
    assert result instanceof Set
    assert result == [1, 2, 3] as Set
    

    When you write this:

    Set<String> unmodifiableFruits = Collections.unmodifiableCollection(fruits)
    

    it implies a .toSet() call to coerce the Collection returned by unmodifiableCollection into a Set, implicitly copying the data.

    When you write this:

    Set<String> unmodifiableFruits = Collections.unmodifiableSet(fruits)
    

    the returned value is already a Set, so toSet() is not called, meaning that unmodifiableFruits and fruits share data.

    That is why you have to explicitly copy the data when using unmodifiableSet, by adding new HashSet(...).

    Is using Collections.unmodifiableCollection() the proper way when passing a set into the constructor?

    Absolutely not. Using unmodifiableCollection() and assigning to a Set, implicitly invoking toSet which copies the data, is hiding the fact that a copy is executed.

    To ensure code readability, i.e. that anyone reading the code (including yourself in 3 years) will understand what it does, write the code to explicitly copy the data, using the copy-constructor.

    Well, of course, unless this is an exercise in code obfuscation, in which case it's a nice misleading trick.