What does Collections.unmodifiableSet() do in Java?

41,979

Solution 1

final declares an object reference that can't be modified, e.g.

private final Foo something = new Foo();

creates a new Foo and places the reference in something. Thereafter, it's not possible to alter something to point to a different instance of Foo.

This does not prevent modification of the internal state of the object. I can still call whatever methods on Foo there are accessible to the relevant scope. If one or more of those methods modifies the internal state of that object, then final won't prevent that.

As such, the following:

private final Set<String> fixed = new HashSet<String>();

does not create a Set that can't be added to or otherwise altered; it just means that fixed will only ever reference that instance.

By contrast, doing:

private Set<String> fixed = Collections.unmodifiableSet( new HashSet<String>() );

creates an instance of a Set which will throw UnsupportedOperationException if one attempts to call fixed.add() or fixed.remove(), for example - the object itself will protect its internal state and prevent it from being modified.

For completeness sake:

private final Set<String> fixed = Collections.unmodifiableSet( new HashSet<String>() );

creates an instance of a Set which won't allow its internal state to be changed, and also means that fixed will only ever point to an instance of that set.

The reason that final can be used to create constants of primitives is based on the fact that the value can't be changed. Remember that fixed above was just a reference - a variable containing an address that can't be changed. Well, for primitives, e.g.

private final int ANSWER = 42;

the value of ANSWER is that 42. Since ANSWER can't be changed, it will only ever have the value 42.

An example that blurs all the lines would be this:

private final String QUESTION = "The ultimate question";

Per the rules above, QUESTION contains the address of an instance of String which represents "The ultimate question", and that address can't be changed. The thing to remember here is that String itself is immutable - you can't do anything to an instance of String which changes it, and any operations which would otherwise do so (such as replace, substring, etc.) return references to entirely different instances of String.

Solution 2

The Collections.unmodifiableSet(Set<? extends T>) will create wrapper on the original set. This wrapper set can not be modified. but still the original set can be modified.

Example:

 Set<String> actualSet=new HashSet<String>(); //Creating set

Adding some elements

actualSet.add("aaa");
actualSet.add("bbb");

Printing added elements

System.out.println(actualSet);   //[aaa, bbb]

Put the actualSet into unmodifiable set and assigned to new reference(wrapperSet).

Set<String> wrapperSet=Collections.unmodifiableSet(orginalSet);

Print the wrapperSet. so it will have actualSet Values

System.out.println(wrapperSet);   //[aaa, bbb]

lets try to remove/add one element on wrapperSet.

wrapperSet.remove("aaa");   //UnSupportedOperationException 

Add one more element in actualSet

    actualSet .add("ccc");

Print actualSet and wrapperSet. both sets values are same. so If you add/remove any elements on actual set the changes will be reflected on wrapper set as well.

    System.out.println(actualSet);  //[aaa, ccc, bbb]
    System.out.println(wrapperSet);  // [aaa, ccc, bbb]

Usage:

This Collections.unmodifiableSet(Set<? extends T>) is used to prevent modification of Set's getter method of any object. let say

public class Department{

    private Set<User> users=new HashSet<User>();

    public Set<User> getUsers(){
        return Collections.unmodifiableSet(users); 
    }
}

Solution 3

final is not (C++-style) const. Unlike C++, Java does not have const-methods or anything like that, and methods that can change the object can be called via a final reference.

Collections.unmodifiable* is a wrapper that enforces (at run time only, not at compile time) the read-only-ness for the collection concerned.

Solution 4

Summarize that we can do and can't:

Preparation:

private Set<String> words = new HashSet<>(Arrays.asList("existing word"));

Final by reference

private final Set<String> words = new HashSet<>();

can:

words.add("new word");

can't:

words = new HashSet<>(); //compilation error

Final by reference and unmodifiable by collection.

private final Set words = Collections.unmodifiableSet(words);

can:

String word = words.iterator().next();

can't:

words = new HashSet<>(); // compilation error
words.add("new word"); // runtime error UnsupportedOperationException

Final by reference and unmodifiable by collection but mutual as collection's object.

But if you have the collection with mutual objects you can CHANGE the inner state of that object.

 class A {
       public int a; //mutable field. I can change it after initialization
       public A(int a) {this.a = a;}
     }

 private final Set<A> set = Collections.unmodifiableSet(Arrays.asList(new A(25)));

Still can't

set = new HashSet<>(); // compilation error
set.add(new A(777)); // runtime error UnsupportedOperationException

But can

 A custom = words.iterator().next(); //here custom.a = 25;
 custom.a = 777; //here first element of **final, unmodifible** collection 
    //was changed from 25 to 777
Share:
41,979
Roman
Author by

Roman

Updated on July 13, 2020

Comments

  • Roman
    Roman almost 4 years

    I can see that Collections.unmodifiableSet returns an unmodifiable view of the given set but I don't understand why we can't just use the final modifier to accomplish this.

    In my understanding, final declares a constant: something that cannot be modified. So, if a set is declared as a constant then it cannot be modified: nothing can be removed from the set and nothing can be added.

    Why do we need Collections.unmodifiableSet?

  • sesquipedalias
    sesquipedalias over 4 years
    however, you can create a final reference to an immutable collection containing mutable objects -- if you then change the contained objects, the collection will appear to change...