Collections.unmodifiableCollection and Collections.unmodifiableSet - java

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.

Related

Confused while testing guava ImmutableList behavior

public class TestImmutableCollection {
static class Helper {
int val;
public Helper(int val) {
this.val = val;
}
#Override
public String toString() {
return String.valueOf(val);
}
}
public static void main(String[] args) {
List<Helper> origin = new ArrayList<>();
origin.add(new Helper(10));
origin.add(new Helper(11));
origin.add(new Helper(13));
ImmutableList<Helper> ul = ImmutableList.copyOf(origin);
ul.get(0).val = 15;
System.out.println(ul);
System.out.println(origin);
}
}
I was asked about immutability in a previous interview thus I search the Internet on Immutable Collections in Java. So I ran into this post Java Immutable Collections where quite a few people referenced that Guava has a better and safer implementation of the Immutable Collections.
Before using guava I have already tested the above code using JDK's built-in UnmodifiableList which turned out the UnmodifiableList is just a wrapper of the original list so that the content of both list will be updated if I use get to access the inner element and then update the field of the element object.
As people stated in the previous post:
Unlike Collections.unmodifiableList(java.util.List<? extends T>),
which is a view of a separate collection that can still change, an
instance of ImmutableList contains its own private data and will never
change.
Then I test the code with guava ImmutableList. But still it gave the same result. Both the content of the ImmutableList created by copyOf() and the original list has changed.
I am quite confused why turns out like that. Am I understanding the scope of immutability here? The change of content of the elements in the Collection won't be judge as a change to the Collection here? But the doc of guava says so absolutely that it will never change.
If so, what is the difference between guava ImmutableList and JDK's UnmodifiableList in this case?
Hope someone can draw a light on that. Appreciate that.
Updated: Well, I know it is not a good design not to add any access constraint on fields of a class. But just try to image a realistic case where the Helper can be like an Account of a user, the filed can be the user's username, you will certainly provide a method to update this username field , right? Then in this case how can I just show the info of the accounts in the list without letting the caller to modify the content of the element in this list?
Neither UnmodifiableList nor ImmutableList guarantee that elements stored in those collections won't ever change. The collections themselves are immutable, not elemenets stored in them. Those collections would have to return copies of stored elements but they don't. You can't add/remove elements to those collections but you can still modify elements themselves.
A quote from Javadocs for ImmutableCollection helps here a lot:
Shallow immutability. Elements can never be added, removed or replaced in this collection. This is a stronger guarantee than that of Collections.unmodifiableCollection(java.util.Collection<? extends T>), whose contents change whenever the wrapped collection is modified.
Basicly UnmodifiableList is just a wrapper around some other collection. If you somehow get hold of that wrapped collection, you can modify it (adding or removing elements) and those changes will be reflected in the UnmodifiableList itself.
ImmutableList on the other hand copies references to all elements of the original collection and doesn't use it anymore. The newly created ImmutableList and the original collection are thus separate, you can modify the original one (adding or removing elements) and won't see any changes in the ImmutableList.
And an extra quote from the Javadocs for ImmutableCollection:
Warning: as with any collection, it is almost always a bad idea to modify an element (in a way that affects its Object.equals(java.lang.Object) behavior) while it is contained in a collection. Undefined behavior and bugs will result. It's generally best to avoid using mutable objects as elements at all, as many users may expect your "immutable" object to be deeply immutable.
Edit to answer the question you added:
If you want caller to be able to do anything with those objects but don't want those changes to affect your original data you can make a deep copy - make copy of every single element in the collection.
Othe approach will be to write a wrapper for that Account class, with restricted access - without any setters. With a simple composition you block all modifications.
You are not modifying the list, but one of the elements inside that list!
If you do the following
public static void main(String[] args) {
List<Helper> origin = new ArrayList<>();
origin.add(new Helper(10));
origin.add(new Helper(11));
origin.add(new Helper(13));
ImmutableList<Helper> ul = ImmutableList.copyOf(origin);
ul.get(0).val = 15;
origin.add(new Helper(42)); // <-- Added another element here!
System.out.println(ul);
System.out.println(origin);
}
you will get the following output:
[15, 11, 13]
[15, 11, 13, 42]
To really achieve immutability, you should consider to also make your elements immutable.

Collectors.toUnmodifiableList in java-10

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.

Static Lists.transform marked as error but Collections.transform using same variables works

I am trying to create an ArrayList based on a jooq Record class using Guava static Collections2.transform and Lists.transform methods. Below is the query that is producing the result3 Record.
final Table<Record3<Key<Store>, Key<Campaign>, String>> c1 = sql.dsl()
.select(CAMPAIGN.STORE_KEY, CAMPAIGN.CAMPAIGN_KEY, tag)
.from(CAMPAIGN)
.where(CAMPAIGN.CAMPAIGN_KEY.equal(campaignKey))
.asTable("c1");
final Table<Record3<Key<Store>, Key<Campaign>, String>> c2 = sql.dsl()
.select(CAMPAIGN.STORE_KEY, CAMPAIGN.CAMPAIGN_KEY, tag)
.from(CAMPAIGN)
.asTable("c2");
final Result<Record2<Key, Integer>> result3 = sql.dsl()
.select(c1Campaign, count(c2Tag))
.from(c1, c2)
.where(c1.field("tag", String.class).equal(c2.field("tag", String.class)))
.and(c1.field("store_key", Key.class).equal(c2.field("store_key", Key.class)))
.and(c1.field("campaign_key", Key.class).notEqual(c2.field("campaign_key", Key.class)))
.groupBy(c2.field("campaign_key", Key.class))
.orderBy(inline(2).desc())
.fetch();
Previously I had the following code which was working fine until I realized I needed to add values to the collection later which is not supported by the collection object that is created.
final Collection<Key<Campaign>> keys = Collections2.transform(result3, Record2::value1);
Because of this issue I tried to switch to creating an ArrayList but when I add the following I get an error saying "Non-static method cannot be referenced from static context"
final ArrayList<Key<Campaign>> keys2 = Lists.transform(result3, Record2::value1);
The confusing thing is both transform methods are static and they are operating on the exact same object so why would the first one work and not the other? Also how can I accomplish generating an ArrayList in this way if the second method does not work?
I discovered there was a second error I saw on compilation that pointed me to the answer. Instead of creating an ArrayList from the Lists.transform I changed it to List and it compiles correctly
Couple basic things you need to know about Java and Guava before actually dealing with the question:
In Java, List<Key> is not ArrayList<Key> (but the opposite is true) - ArrayList is an implementation (one of many possible) of an interface List.
You can't assign List to ArrayList (at least without casting, but it's not the solution nor the option here).
Even though you create ArrayList, it's not the best practice to use concrete implementation in your code, but rather stick to interface (here: List).
Guava's Collections2.transform and Lists.transform return lazy views of collections you used to create the view (from javadoc, emphasis mine):
The returned list is a transformed view of fromList; changes to fromList will be reflected in the returned list and vice versa.
(...)
The function is applied lazily, invoked when needed. This is necessary for the returned list to be a view, but it means that the function will be applied many times for bulk operations like List.contains(java.lang.Object) and List.hashCode(). For this to perform well, function should be fast. To avoid lazy evaluation when the returned list doesn't need to be a view, copy the returned list into a new list of your choosing.
Having all that said, keys in your case won't evaluate anything until copied to some collection / list or consumed, so valid approach would be:
final Collection<Key<Campaign>> keys = Collections2.transform(result3, Record2::value1);
// later:
final List<Key<Campaign>> keys2 = new ArrayList<>(keys); // here the function is applied
or
final List<Key<Campaign>> keys2 = new ArrayList<>(Lists.transform(result3, Record2::value1));
What bothers me is that you use method references (Record2::value1) which indicates you're already using Java 8. If you do, please use Streams API:
List<Key<Campaign>> keys = result3.stream()
.map(Record2::value1)
.collect(Collectors.toList());

How to copy a java.util.List into another java.util.List

I have a List<SomeBean> that is populated from a Web Service. I want to copy/clone the contents of that list into an empty list of the same type. A Google search for copying a list suggested me to use Collections.copy() method. In all the examples I saw, the destination list was supposed to contain the exact number of items for the copying to take place.
As the list I am using is populated through a web service and it contains hundreds of objects, I cannot use the above technique. Or I am using it wrong??!! Anyways, to make it work, I tried to do something like this, but I still got an IndexOutOfBoundsException.
List<SomeBean> wsList = app.allInOne(template);
List<SomeBean> wsListCopy=new ArrayList<SomeBean>(wsList.size());
Collections.copy(wsListCopy,wsList);
System.out.println(wsListCopy.size());
I tried to use the wsListCopy=wsList.subList(0, wsList.size()) but I got a ConcurrentAccessException later in the code. Hit and trial. :)
Anyways, my question is simple, how can I copy the entire content of my list into another List? Not through iteration, of course.
Just use this:
List<SomeBean> newList = new ArrayList<SomeBean>(otherList);
Note: still not thread safe, if you modify otherList from another thread, then you may want to make that otherList (and even newList) a CopyOnWriteArrayList, for instance -- or use a lock primitive, such as ReentrantReadWriteLock to serialize read/write access to whatever lists are concurrently accessed.
This is a really nice Java 8 way to do it:
List<String> list2 = list1.stream().collect(Collectors.toList());
Of course the advantage here is that you can filter and skip to only copy of part of the list.
e.g.
//don't copy the first element
List<String> list2 = list1.stream().skip(1).collect(Collectors.toList());
originalArrayList.addAll(copyArrayofList);
Please keep on mind whenever using the addAll() method for copy, the contents of both the array lists (originalArrayList and copyArrayofList) references to the same objects will be added to the list so if you modify any one of them then copyArrayofList also will also reflect the same change.
If you don't want side effect then you need to copy each of element from the originalArrayList to the copyArrayofList, like using a for or while loop. for deep copy you can use below code snippet.
but one more thing you need to do, implement the Cloneable interface and override the clone() method for SomeBean class.
public static List<SomeBean> cloneList(List<SomeBean> originalArrayList) {
List<SomeBean> copyArrayofList = new ArrayList<SomeBean>(list.size());
for (SomeBean item : list) copyArrayofList.add(item.clone());
return copyArrayofList;
}
I tried to do something like this, but I still got an IndexOutOfBoundsException.
I got a ConcurrentAccessException
This means you are modifying the list while you are trying to copy it, most likely in another thread. To fix this you have to either
use a collection which is designed for concurrent access.
lock the collection appropriately so you can iterate over it (or allow you to call a method which does this for you)
find a away to avoid needing to copy the original list.
Starting from Java 10:
List<E> oldList = List.of();
List<E> newList = List.copyOf(oldList);
List.copyOf() returns an unmodifiable List containing the elements of the given Collection.
The given Collection must not be null, and it must not contain any null elements.
Also, if you want to create a deep copy of a List, you can find many good answers here.
There is another method with Java 8 in a null-safe way.
List<SomeBean> wsListCopy = Optional.ofNullable(wsList)
.map(Collection::stream)
.orElseGet(Stream::empty)
.collect(Collectors.toList());
If you want to skip one element.
List<SomeBean> wsListCopy = Optional.ofNullable(wsList)
.map(Collection::stream)
.orElseGet(Stream::empty)
.skip(1)
.collect(Collectors.toList());
With Java 9+, the stream method of Optional can be used
Optional.ofNullable(wsList)
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList())
I tried something similar and was able to reproduce the problem (IndexOutOfBoundsException). Below are my findings:
1) The implementation of the Collections.copy(destList, sourceList) first checks the size of the destination list by calling the size() method. Since the call to the size() method will always return the number of elements in the list (0 in this case), the constructor ArrayList(capacity) ensures only the initial capacity of the backing array and this does not have any relation to the size of the list. Hence we always get IndexOutOfBoundsException.
2) A relatively simple way is to use the constructor that takes a collection as its argument:
List<SomeBean> wsListCopy=new ArrayList<SomeBean>(wsList);
I was having the same problem ConcurrentAccessException and mysolution was to:
List<SomeBean> tempList = new ArrayList<>();
for (CartItem item : prodList) {
tempList.add(item);
}
prodList.clear();
prodList = new ArrayList<>(tempList);
So it works only one operation at the time and avoids the Exeption...
You can use addAll().
eg : wsListCopy.addAll(wsList);
re: indexOutOfBoundsException, your sublist args are the problem; you need to end the sublist at size-1. Being zero-based, the last element of a list is always size-1, there is no element in the size position, hence the error.
I can't see any correct answer. If you want a deep copy you have to iterate and copy object manually (you could use a copy constructor).
You should use the addAll method. It appends all of the elements in the specified collection to the end of the copy list. It will be a copy of your list.
List<String> myList = new ArrayList<>();
myList.add("a");
myList.add("b");
List<String> copyList = new ArrayList<>();
copyList.addAll(myList);
just in case you use Lombok:
mark SomeBean with the following annotation:
#Builder(toBuilder = true, builderMethodName = "")
and Lombok will perform a shallow copy of objects for you using copy constructor:
inputList.stream()
.map(x -> x.toBuilder().build())
.collect(Collectors.toList());
subList function is a trick, the returned object is still in the original list.
so if you do any operation in subList, it will cause the concurrent exception in your code, no matter it is single thread or multi thread.

Use of Java's Collections.singletonList()?

What is the use of Collections.singletonList() in Java? I understand that it returns a list with one element. Why would I want to have a separate method to do that? How does immutability play a role here?
Are there any special useful use-cases for this method rather than just being a convenient method?
The javadoc says this:
"Returns an immutable list containing only the specified object. The returned list is serializable."
You ask:
Why would I want to have a separate method to do that?
Primarily as a convenience ... to save you having to write a sequence of statements to:
create an empty list object
add an element to it, and
wrap it with an immutable wrapper.
It may also be a bit faster and/or save a bit of memory, but it is unlikely that these small savings will be significant. (An application that creates vast numbers of singleton lists is unusual to say the least.)
How does immutability play a role here?
It is part of the specification of the method; see above.
Are there any special useful use-cases for this method, rather than just being a convenience method?
Clearly, there are use-cases where it is convenient to use the singletonList method. Indeed, any program where you need to use an immutable list with one element is a valid use-case. (It takes roughly zero imagination to think of one.)
But I don't know how you would (objectively) distinguish between an ordinary use-case and a "specially useful" one ...
From the javadoc
#param the sole object to be stored in the returned list.
#return an immutable list containing only the specified object.
example
import java.util.*;
public class HelloWorld {
public static void main(String args[]) {
// create an array of string objs
String initList[] = { "One", "Two", "Four", "One",};
// create one list
List list = new ArrayList(Arrays.asList(initList));
System.out.println("List value before: "+list);
// create singleton list
list = Collections.singletonList("OnlyOneElement");
list.add("five"); //throws UnsupportedOperationException
System.out.println("List value after: "+list);
}
}
Use it when code expects a read-only list, but you only want to pass one element in it. singletonList is (thread-)safe and fast.
Here's one view on the singleton methods:
I have found these various "singleton" methods to be useful for passing a single value to an API that requires a collection of that value. Of course, this works best when the code processing the passed-in value does not need to add to the collection.
To answer your immutable question:
Collections.singletonList will create an immutable List.
An immutable List (also referred to as an unmodifiable List) cannot have it's contents changed. The methods to add or remove items will throw exceptions if you try to alter the contents.
A singleton List contains only that item and cannot be altered.
If an Immutable/Singleton collections refers to the one which having only one object and which is not further gets modified, then the same functionality can be achieved by making a collection "UnmodifiableCollection" having only one object. Since the same functionality can be achieved by Unmodifiable Collection with one object, then what special purpose the Singleton Collection serves for?
singletonList can hold instance of any object. Object state can be modify.
List<Character> list = new ArrayList<Character>();
list.add('X');
list.add('Y');
System.out.println("Initial list: "+ list);
List<List<Character>> list2 = Collections.singletonList(list);
list.add('Z');
System.out.println(list);
System.out.println(list2);
We can not define unmodifiableList like above.

Categories

Resources