Collectors.toUnmodifiableList in java-10 - java

How do you create an Unmodifiable List/Set/Map with Collectors.toList/toSet/toMap, since toList (and the like) are document as :
There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned
Before java-10 you have to provide a Function with Collectors.collectingAndThen, for example:
List<Integer> result = Arrays.asList(1, 2, 3, 4)
.stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
x -> Collections.unmodifiableList(x)));

With Java 10, this is much easier and a lot more readable:
List<Integer> result = Arrays.asList(1, 2, 3, 4)
.stream()
.collect(Collectors.toUnmodifiableList());
Internally, it's the same thing as Collectors.collectingAndThen, but returns an instance of unmodifiable List that was added in Java 9.

Additionally to clear out a documented difference between the two(collectingAndThen vs toUnmodifiableList) implementations :
The Collectors.toUnmodifiableList would return a Collector that
disallows null values and will throw NullPointerException if it is
presented with a null value.
static void additionsToCollector() {
// this works fine unless you try and operate on the null element
var previous = Stream.of(1, 2, 3, 4, null)
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
// next up ready to face an NPE
var current = Stream.of(1, 2, 3, 4, null).collect(Collectors.toUnmodifiableList());
}
and furthermore, that's owing to the fact that the former constructs an instance of Collections.UnmodifiableRandomAccessList while the latter constructs an instance of ImmutableCollections.ListN which adds to the list of attributes brought to the table with static factory methods.

Stream#toList
Java 16 adds a method on the Stream interface: toList(). To quote the Javadoc:
The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown.
Not just more convenient than Collectors, this method has some goodies like better performance on parallel streams.
In particular with parallel() -- as it avoids result copying.
Benchmark is a few simple ops on 100K elem stream of Long.
For a further reading please go to: http://marxsoftware.blogspot.com/2020/12/jdk16-stream-to-list.html
In resume the link states something like this.
Gotcha: It may be tempting to go into one's code base and use
stream.toList() as a drop-in replacement for
stream.collect(Collectors.toList()), but there may be differences in
behavior if the code has a direct or indirect dependency on the
implementation of stream.collect(Collectors.toList()) returning an
ArrayList. Some of the key differences between the List returned by
stream.collect(Collectors.toList()) and stream.toList() are spelled
out in the remainder of this post.
The Javadoc-based documentation for Collectors.toList() states
(emphasis added), "Returns a Collector that accumulates the input
elements into a new List. There are no guarantees on the type,
mutability, serializability, or thread-safety of the List returned..."
Although there are no guarantees regarding the "type, mutability,
serializability, or thread-safety" on the List provided by
Collectors.toList(), it is expected that some may have realized it's
currently an ArrayList and have used it in ways that depend on the
characteristics of an ArrayList
What i understand is that the Stream.toList() it will result in a inmutable List.
Stream.toList() provides a List implementation that is immutable (type
ImmutableCollections.ListN that cannot be added to or sorted) similar
to that provided by List.of() and in contrast to the mutable (can be
changed and sorted) ArrayList provided by
Stream.collect(Collectors.toList()). Any existing code depending on
the ability to mutate the ArrayList returned by
Stream.collect(Collectors.toList()) will not work with Stream.toList()
and an UnsupportedOperationException will be thrown.
Although the implementation nature of the Lists returned by
Stream.collect(Collectors.toList()) and Stream.toList() are very
different, they still both implement the List interface and so they
are considered equal when compared using List.equals(Object)
And this method will allow nulls so starting from Java 16 we will have a
mutable/null-friendly----->Collectors.toList()
immutable/null-friendly--->Stream.toList()
immutable/null-hostile---->Collectors.toUnmodifiableList() //Naughty
It's great.

List/Set/Map.copyOf
You asked:
How do you create an Unmodifiable List/Set/Map
As of Java 10, simply pass your existing list/set/map to:
List.copyOf
Set.copyOf
Map.copyOf
These static methods return an unmodifiable List, unmodifiable Set, or unmodifiable Map, respectively. Read the details on those linked Javadoc pages.
No nulls allowed.
If the passed collection is already unmodifiable, that passed collection is simply returned, no further work, no new collection.
Note: If using the convenient Stream#toList method in Java 16+ as described in this other Answer, there is no point to this solution here, no need to call List.copyOf. The result of toList is already unmodifiable.

Related

Should I use shared mutable variable update in Java 8 Streams

Just iterating below list & adding into another shared mutable list via java 8 streams.
List<String> list1 = Arrays.asList("A1","A2","A3","A4","A5","A6","A7","A8","B1","B2","B3");
List<String> list2 = new ArrayList<>();
Consumer<String> c = t -> list2.add(t.startsWith("A") ? t : "EMPTY");
list1.stream().forEach(c);
list1.parallelStream().forEach(c);
list1.forEach(c);
What is the difference between above three iteration & which one we need to use. Are there any considerations?
Regardless of whether you use parallel or sequential Stream, you shouldn't use forEach when your goal is to generate a List. Use map with collect:
List<String> list2 =
list2.stream()
.map(item -> item.startsWith("A") ? item : "EMPTY")
.collect(Collectors.toList());
Functionally speaking,for the simple cases they are almost the same, but generally speaking, there are some hidden differences:
Lets start by quoting from Javadoc of forEach for iterable use-cases stating that:
performs the given action for each element of the Iterable until all
elements have been processed or the action throws an exception.
and also we can iterate over a collection and perform a given action on each element – by just passing a class that implements the Consumer interface
void forEach(Consumer<? super T> action)
https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer-
The order of Stream.forEach is random while Iterable.forEach is always executed in the iteration order of the Iterable.
If Iterable.forEach is iterating over a synchronized collection, Iterable.forEach takes the collection's lock once and holds it across all the calls to the action method. The Stream.forEach call uses the collection's spliterator, which does not lock
The action specified in Stream.forEach is required to be non-interfering while Iterable.forEach is allowed to set values in the underlying ArrayList without problems.
In Java, Iterators returned by Collection classes, e.g. ArrayList, HashSet, Vector, etc., are fail fast. This means that if you try to add() or remove() from the underlying data structure while iterating it, you get a ConcurrentModificationException.
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#fail-fast
More Info:
What is the difference between .foreach and .stream().foreach?
What is difference between Collection.stream().forEach() and Collection.forEach()?
When working with streams, you should write your code in a way that if you switch to parallel streams, it does not produce the wrong results.
Imagine if in your code you were doing reading and writing on the same shared memory (list2) and you distribute your process into several threads (using parallel streams). Then you are DOOMED. Therefore you have several options.
make your shared memory (list2) thread-safe. for example by using AtomicReferences
List<String> list2 = new ArrayList<>();
AtomicReference<List<String>> listSafe = new AtomicReference<>();
listSafe.getAndUpdate(strings -> {strings.add("newvalue"); return strings;});
or you can go with the purely functional approach (code with no side effects)
like the #Eran solution.

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:
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).
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?
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.

Usage of Java 9 collection factories

In the context of the comments and answers given at List.of() or Collections.emptyList() and List.of(...) or Collections.unmodifiableList() I came up with two following rules of thumb (which also apply to Set and Map factories accordingly).
Don't replace all occurrences
Keep using Collections.emptyList() for readability and when e.g. initializing lazy field members like:
class Bean {
private List<Bean> beans = Collection.emptyList();
public List<Bean> getBeans() {
if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }
return beans;
}
}
Use new factories as method argument builders
Use new factories List.of() and variants as quick and less-to-type version, when calling an executable with List parameter(s). Here are my current substitution patterns:
Collections.emptyList() --> List.of()
Collections.singletonList(a) --> List.of(a)
Arrays.asList(a, ..., z) --> List.of(a, ..., z)
In a fictional usage of Collections.indexOfSubList, the following lines
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.emptyList());
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.singletonList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(2, 3));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3));
will read
Collections.indexOfSubList(List.of(1, 2, 3), List.of());
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(2, 3));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1, 2, 3));
Do you (dis-)agree?
Generally, use of the new factories is safe for new code, where there is no existing code that depends on behaviors of the existing collections.
There are several reasons the new collection factories aren't drop-in replacements for code that initializes collections using the existing APIs. Obviously immutability is one of the most prominent reasons; if you need to modify the collection later, it obviously can't be immutable! But there are other reasons as well, some of them quite subtle.
For an example of replacement of existing APIs with the new APIs, see JDK-8134373. The review threads are here: Part1 Part2.
Here's a rundown of the issues.
Array Wrapping vs Copying. Sometimes you have an array, e.g. a varargs parameter, and you want to process it as a list. Sometimes Arrays.asList is the most appropriate thing here, as it's just a wrapper. By contrast, List.of creates a copy, which might be wasteful. On the other hand, the caller still has a handle to the wrapped array and can modify it, which might be a problem, so sometimes you want to pay the expense of copying it, for example, if you want to keep a reference to the list in an instance variable.
Hashed Collection Iteration Order. The new Set.of and Map.of structures randomize their iteration order. The iteration order of HashSet and HashMap is undefined, but in practice it turns out to be relatively stable. Code can develop inadvertent dependencies on iteration order. Switching to the new collection factories may expose old code to iteration order dependencies, surfacing latent bugs.
Prohibition of Nulls. The new collections prohibit nulls entirely, whereas the common non-concurrent collections (ArrayList, HashMap) allow them.
Serialization Format. The new collections have a different serialization format from the old ones. If the collection is serialized, or it's stored in some other class that's serialized, the serialized output will differ. This might or might not be an issue. But if you expect to interoperate with other systems, this could be a problem. In particular, if you transmit the serialized form of the new collections to a Java 8 JVM, it will fail to deserialize, because the new classes don't exist on Java 8.
Strict Mutator Method Behavior. The new collections are immutable, so of course they throw UnsupportedOperationException when mutator methods are called. There are some edge cases, however, where behavior is not consistent across all the collections. For example,
Collections.singletonList("").addAll(Collections.emptyList())
does nothing, whereas
List.of("").addAll(Collections.emptyList())
will throw UOE. In general, the new collections and the unmodifiable wrappers are consistently strict in throwing UOE on any call to a mutator method, even if no actual mutation would occur. Other immutable collections, such as those from Collections.empty* and Collections.singleton*, will throw UOE only if an actual mutation would occur.
Duplicates. The new Set and Map factories reject duplicate elements and keys. This is usually not a problem if you're initializing a collection with a list of constants. Indeed, if a list of constants has a duplicate, it's probably a bug. Where this is potentially an issue is when a caller is allowed to pass in a collection or array (e.g., varags) of elements. If the caller passes in duplicates, the existing APIs would silently omit the duplicates, whereas the new factories will throw IllegalArgumentException. This is a behavioral change that might impact callers.
None of these issues are fatal problems, but they are behavioral differences that you should be aware of when retrofitting existing code. Unfortunately this means that doing a mass replacement of existing calls with the new collection factories is probably ill-advised. It's probably necessary to do some inspection at each site to assess any potential impact of the behavioral changes.
(Im)Mutability
First of all, it is important to note that the collection factories return immutable variants. Unfortunately, this does not show in the type system so you have to track that manually / mentally. This already forbids some replacements that might otherwise be worthwile, so it must become 0. in your list of rules. :)
For example, creating a collection of seed elements that are later modified by other code might look like this:
private final Set<String> commonLetters = initialCommonLetters()
private static Set<String> initialCommonLetters() {
Set<String> letters = new HashSet<>();
letters.add("a");
letters.add("e");
return letters;
}
Would be great to simply write commonLetters = Set.of("a", "e"); but this will likely break other code as the returned set is immutable.
Constants
The (im)mutability discussion immediately leads to constants. This was a major reason to introduce them! Gone are the days where you need a static initializer block to create that COMMON_LETTERS constant. This would hence be the place where I would look first for use cases.
Replacing
As you say, there seems to be no reason to start replacing calls to Collections::empty..., Collections::singleton..., or Arrays::asList just for the fun of it. What I would do, though, as soon as I start using the new methods in a class I would replace the old variants as well to have the code rely on fewer concepts, making understanding it easier.
Preference
The last argument is also one that could apply to the of() variants in general. While Collections::empty... and Collections::singleton... are somewhat clearer about their intent, I slightly tend towards saying that always using of, no matter how many arguments you have, offsets that advantage by writing code that, as a whole, uses less concepts.
I see no reason to continue using Arrays::asList.

Immutable vs Unmodifiable collection [duplicate]

This question already has answers here:
Java Immutable Collections
(7 answers)
Closed 9 months ago.
From the Collections Framework Overview:
Collections that do not support modification operations (such as add, remove and clear) are referred to as unmodifiable. Collections that are not unmodifiable are modifiable.
Collections that additionally guarantee that no change in the Collection object will be visible are referred to as immutable. Collections that are not immutable are mutable.
I cannot understand the distinction.
What is the difference between unmodifiable and immutable here?
An unmodifiable collection is often a wrapper around a modifiable collection which other code may still have access to. So while you can't make any changes to it if you only have a reference to the unmodifiable collection, you can't rely on the contents not changing.
An immutable collection guarantees that nothing can change the collection any more. If it wraps a modifiable collection, it makes sure that no other code has access to that modifiable collection. Note that although no code can change which objects the collection contains references to, the objects themselves may still be mutable - creating an immutable collection of StringBuilder doesn't somehow "freeze" those objects.
Basically, the difference is about whether other code may be able to change the collection behind your back.
Basically unModifiable Collection is a view, So indirectly it could still be 'modified' from some other reference that is modifiable. Also as its just a readonly view of annother collection , When the source collection changes unModifiable Collection will always present with latest values.
However immutable Collection can be treated as a readonly copy of another collection and can not be modified. In this case when the source collection changes , immutable Collection do not reflect the changes
Here is a testcase to visualise this difference.
#Test
public void testList() {
List<String> modifiableList = new ArrayList<String>();
modifiableList.add("a");
System.out.println("modifiableList:"+modifiableList);
System.out.println("--");
//unModifiableList
assertEquals(1, modifiableList.size());
List<String> unModifiableList=Collections.unmodifiableList(
modifiableList);
modifiableList.add("b");
boolean exceptionThrown=false;
try {
unModifiableList.add("b");
fail("add supported for unModifiableList!!");
} catch (UnsupportedOperationException e) {
exceptionThrown=true;
System.out.println("unModifiableList.add() not supported");
}
assertTrue(exceptionThrown);
System.out.println("modifiableList:"+modifiableList);
System.out.println("unModifiableList:"+unModifiableList);
assertEquals(2, modifiableList.size());
assertEquals(2, unModifiableList.size());
System.out.println("--");
//immutableList
List<String> immutableList=Collections.unmodifiableList(
new ArrayList<String>(modifiableList));
modifiableList.add("c");
exceptionThrown=false;
try {
immutableList.add("c");
fail("add supported for immutableList!!");
} catch (UnsupportedOperationException e) {
exceptionThrown=true;
System.out.println("immutableList.add() not supported");
}
assertTrue(exceptionThrown);
System.out.println("modifiableList:"+modifiableList);
System.out.println("unModifiableList:"+unModifiableList);
System.out.println("immutableList:"+immutableList);
System.out.println("--");
assertEquals(3, modifiableList.size());
assertEquals(3, unModifiableList.size());
assertEquals(2, immutableList.size());
}
Output
modifiableList:[a]
--
unModifiableList.add() not supported
modifiableList:[a, b]
unModifiableList:[a, b]
--
immutableList.add() not supported
modifiableList:[a, b, c]
unModifiableList:[a, b, c]
immutableList:[a, b]
--
I think the main difference is that the owner of a mutable collection might want to provide access to the collection to some other code, but provide that access through an interface that doens't allow the other code to modify the collection (while reserving that capability to the owning code). So the collection isn't immutable, but certain users aren't permitted to change the collection.
Oracle's Java Collection Wrapper tutorial has this to say (emphasis added):
Unmodifiable wrappers have two main uses, as follows:
To make a collection immutable once it has been built. In this case, it's good practice not to maintain a reference to the backing
collection. This absolutely guarantees immutability.
To allow certain clients read-only access to your data structures. You keep a reference to the backing collection but hand
out a reference to the wrapper. In this way, clients can look but not
modify, while you maintain full access.
An object is considered immutable if its state cannot change after it is constructed. After you create an immutable instance of a collection, it holds the same data as long as a reference to it exists.
One advantage of an immutable collection is that it is automatically thread safe. Collections containing immutable objects are automatically thread safe after construction. After you create such a collection, you can hand it to multiple threads, and they will all see a consistent view.
However, an immutable collection of objects is not the same as a collection of immutable objects. If the contained elements are mutable, then this may cause the collection to behave inconsistently or make its contents to appear to change.
In simple words, if you add a little immutability to something mutable, you get mutability. And if you add a little mutability to something immutable, you get mutability.
Immutable and Unmodifiable Are Not the Same :
The immutable collections behave in the same way as the Collections.unmodifiable... wrappers. However, these collections are not wrappers — these are data structures implemented by classes where any attempt to modify the data causes an exception to be thrown.
If you create a List and pass it to the Collections.unmodifiableList method, then you get an unmodifiable view. The underlying list is still modifiable, and modifications to it are visible through the List that is returned, so it is not actually immutable.
To demonstrate this behavior, create a List and pass it to Collections.unmodifiableList. If you try to add to that unmodifiable List directly, then an UnsupportedOperationException is thrown.
But, if you change the original List, no error is generated, and the unmodifiable List has been modified.
In this case, to make a collection immutable once it has been built, it's a good practice not to maintain a reference to the backing collection. This absolutely guarantees immutability.
Further, to allow certain clients read-only access to your data structures. You can keep a reference to the backing collection but hand out a reference to the wrapper. In this way, clients can look but not be able to modify, while you maintain full access.
So, an immutable collection can contain mutable objects, and if it does, the collection is neither immutable nor thread safe.
To quote The Java™ Tutorials:
Unlike synchronization wrappers, which add functionality to the wrapped collection, the unmodifiable wrappers take functionality away. In particular, they take away the ability to modify the collection by intercepting all the operations that would modify the collection and throwing an UnsupportedOperationException. Unmodifiable wrappers have two main uses, as follows:
To make a collection immutable once it has been built. In this case, it's good practice not to maintain a reference to the backing collection. This absolutely guarantees immutability.
To allow certain clients read-only access to your data structures. You keep a reference to the backing collection but hand out a reference to the wrapper. In this way, clients can look but not modify, while you maintain full access.
(emphasis mine)
This really sums it up.
If we are talking about JDK Unmodifiable* vs guava Immutable*, actually the difference is also in performance. Immutable collections can be both faster and more memory-efficient if they are not wrappers around regular collections (JDK implementations are wrappers).
Citing the guava team:
The JDK provides Collections.unmodifiableXXX methods, but in our opinion, these can be
<...>
inefficient: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.
// normal list
List list1 = new ArrayList();
list1.add(1);
// unmodifiable list
List list2 = Collections.unmodifiableList(list1);
// immutable list
List list3 = Collections.unmodifiableList(new ArrayList<>(list1));
list1.add(2);
list1.add(3);
System.out.println(list1);
System.out.println(list2);
System.out.println(list3);
Output:
[1, 2, 3]
[1, 2, 3]
[1]
Unmodifiable vs Immutable Collection
Create A modifiable map
Map<String, String> modifiableMap = new HashMap();
modifiableMap.put(“1”,”one”);
modifiableMap.put(“2”,”two”);
modifiableMap.put(“3”,”three”);
Create an unmodifiableMap out of modifiableMap
Map<String,String> unmodifiableMap = Collections.unmodifiableMap(modifiableMap);
unmodifiableMap.put(“4”,”Four”) ==>Exception
modifiableMap.put(“4”,”Four”); ==>Allowed, this will also reflect now in the unmodifiableMap , because unmodifiableMap() returns a wrapper around modifiableMap.
Create an immutableMap out of modifiableMap
Map<String,String> immutableMap = Collections.immutableMap(modifiableMap);
immutableMap.put(“5”,”Five”) ==>Exception
modifiableMap.put(“5”,”Five”); ==>Allowed, BUT this will NOT reflect now in the immutableMap, because immutableMap() returns a copy of the modifiableMap.
As noted above unmodifiable is not like immutable because an unmodifiable collection can be altered if for example an unmodifiable collection has an underlying delegate collection which is referenced by some other object and that object changes it.
Regarding immutable, it's not even well defined. However, generally it means that the object "will not change", but that would need to be defined recursively. For example, I can define immutable on classes whose instance variables are all primitives and whose methods all contain no arguments and return primitives. The methods then recursively allow the instance variables to be immutable and all methods to contain arguments that are immutable and that return immutable values. The methods should be guaranteed to return the same value over time.
Assuming that we can do that, there is also the concept thread safe. And you might be led to believe that immutable (or not changeble over time) also implies thread safe. However that is not the case and that is the main point I am making here that has not yet been noted in other answers. I can construct an immutable object that always returns the same results yet is not thread safe. To see this suppose that I construct an immutable collection by maintaining additions and deletions over time. Now the immutable collection returns its elements by looking at the internal collection (which may be changing over time) and then (internally) adding and deleting the elements that were added or deleted after creation of the collection. Clearly, although the collection would always return the same elements, it is not thread safe merely because it will never change value.
Now we can define immutable as objects that are thread safe and will never change. There are guidelines for creating immutable classes that generally lead to such classes, however, keep in mind that there may be ways to create immutable classes, that require attention to thread safety, for example, as described in the "snapshot" collection example above.
The Java™ Tutorials say the following:
Unlike synchronization wrappers, which add functionality to the
wrapped collection, the unmodifiable wrappers take functionality away.
In particular, they take away the ability to modify the collection by
intercepting all the operations that would modify the collection and
throwing an UnsupportedOperationException. Unmodifiable wrappers have
two main uses, as follows:
To make a collection immutable once it has been built. In this case,
it's good practice not to maintain a reference to the backing
collection. This absolutely guarantees immutability.
To allow certain clients read-only access to your data structures. You
keep a reference to the backing collection but hand out a reference to
the wrapper. In this way, clients can look but not modify, while you
maintain full access.
I think its a good enough explanation to understand the difference.
[Unmodifiable and Immutable]
Unmodifiable collection(object) can still be changed by changing origin object. It is possible using reference.
Java provides with several ways to create an Unmodifiable map:
Collections.unmodifiableMap()
Java 9 Map.of(), Map.ofEntries()

What's the difference between Collections.unmodifiableSet() and ImmutableSet of Guava?

JavaDoc of ImmutableSet says:
Unlike Collections.unmodifiableSet, which is a view of a separate collection that can still change, an instance of this class contains its own private data and will never change. This class is convenient for public static final sets ("constant sets") and also lets you easily make a "defensive copy" of a set provided to your class by a caller.
But the ImmutableSet still stores reference of elements, I couldn't figure out the difference to Collections.unmodifiableSet(). Sample:
StringBuffer s=new StringBuffer("a");
ImmutableSet<StringBuffer> set= ImmutableSet.of(s);
s.append("b");//s is "ab", s is still changed here!
Could anyone explain it?
Consider this:
Set<String> x = new HashSet<String>();
x.add("foo");
ImmutableSet<String> guava = ImmutableSet.copyOf(x);
Set<String> builtIn = Collections.unmodifiableSet(x);
x.add("bar");
System.out.println(guava.size()); // Prints 1
System.out.println(builtIn.size()); // Prints 2
In other words, ImmutableSet is immutable despite whatever collection it's built from potentially changing - because it creates a copy. Collections.unmodifiableSet prevents the returned collection from being directly changed, but it's still a view on a potentially-changing backing set.
Note that if you start changing the contents of the objects referred to by any set, all bets are off anyway. Don't do that. Indeed, it's rarely a good idea to create a set using a mutable element type in the first place. (Ditto maps using a mutable key type.)
Besides the behavioral difference that Jon mentions, an important difference between ImmutableSet and the Set created by Collections.unmodifiableSet is that ImmutableSet is a type. You can pass one around and have it remain clear that the set is immutable by using ImmutableSet rather than Set throughout the code. With Collections.unmodifiableSet, the returned type is just Set... so it's only clear that the set is unmodifiable at the point where it is created unless you add Javadoc everywhere you pass that Set saying "this set is unmodifiable".
Kevin Bourrillion (Guava lead developer) compares immutable / unmodifiable collections in this presentation. While the presentation is two years old, and focuses on "Google Collections" (which is now a subpart of Guava), this is a very interesting presentation. The API may have changed here and there (the Google Collections API was in Beta at the time), but the concepts behind Google Collections / Guava are still valid.
You might also be interested in this other SO question ( What is the difference between google's ImmutableList and Collections.unmodifiableList() ).
A difference between the two not stated in other answers is that ImmutableSet does not permit null values, as described in the Javadoc
A high-performance, immutable Set with reliable, user-specified iteration order. Does not permit null elements.
(The same restriction applies to values in all Guava immutable collections.)
For example:
ImmutableSet.of(null);
ImmutableSet.builder().add("Hi").add(null); // Fails in the Builder.
ImmutableSet.copyOf(Arrays.asList("Hi", null));
All of these fail at runtime. In contrast:
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("Hi", null)));
This is fine.

Categories

Resources