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.
Related
Is it possible to find out if some a list is fixed size or not?
I mean, for example this code:
String[] arr = {"a", "b"};
List<String> list = Arrays.asList(array);
returns fixed size List backed by an array. But is it possible to understand programmatically if List is fixed-size or not without trying to add/remove elements and catching the exception? For example:
try {
list.add("c");
}
catch(UnsupportedOperationException e) {
// Fixed-size?
}
A list created from a String[] by
List<String> list = Arrays.asList(array);
will have Arrays as enclosing class, while one created by for example new ArrayList() won't have the enclosing class. So the following should work to check if the List was produced as a result of calling Arrays.toList():
static <T> boolean wasListProducedAsAResultOfCallingTheFunctionArrays_asList(List<T> l) {
return Arrays.class.equals(l.getClass().getEnclosingClass());
}
Beware that this method relies on undocumented behavior. It will break if they added another nested List subclass to the Arrays class.
Is it possible to find out if some list is fixed size or not?
In theory - No. Fixed sizedness is an emergent property of the implementation of a list class. You can only determine if a list has that property by trying to add an element.
And note that a simple behavioral test would not reliably distinguish between a fixed sized list and a bounded list or a list that was permanently or temporarily read-only.
In practice, a fixed sized list will typically have a different class to an ordinary one. You can test the class of an object to see if it or isn't a specific class. So if you understand what classes would be used to implement fixed sized lists in your code-base, then you can test if a specific list is fixed sized.
For example the Arrays.asList(...) method returns a List object whose actual class is java.util.Arrays.ArrayList. That is a private nested class, but you could use reflection find it, and then use Object.getClass().equals(...) to test for it.
However, this approach is fragile. Your code could break if the implementation of Arrays was modified, or if you started using other forms of fixed sized list as well.
No.
The List API is identical regardless of whether a List is expandable or not, something that was deliberate.
There is also nothing in the List API that allows you to query it to determine this feature.
You can't completely reliably determine this information by reflection, because you will be depending on internal details of the implementation, and because there is an unbounded number of classes that are potentially fixed-size. For example, in addition to Arrays.asList, there is also Arrays.asList().subList, which happens to return a different class. There can also be wrappers around the base list like Collections.checkedList, Collections.synchronizedList and Collections.unmodifiableList. There are also other fixed-size lists: Collections.emptyList, Collections.singletonList, and Collections.nCopies. Outside the standard library, there are things like Guava's ImmutableList. It's also pretty trivial to hand-roll a list for something by extending AbstractList (for a fixed-size list you need only implement the size() and get(int) methods).
Even if you detect that your list is not fixed-size, the specification of List.add allows it to refuse elements for other reasons. For example, Collections.checkedList wrappers throw a ClassCastException for elements of unwanted type.
And even if you know your list is expandable, and allows arbitrary elements, that doesn't mean you want to use it. Perhaps it's synchronized, or not synchronized, or isn't serializable, or it's a slow linked list, or has some other quality that you don't want.
If you want control over the type, mutability, serializability, or thread-safety of the list, or you want to be sure that no other code has kept a reference to it, the practice is that you create a new one yourself. It's not expensive to do so when unnecessary (memcopies are blazing fast), and it lets you reason more definitely about your code will actually do at runtime. If you'd really like to avoid creating unnecessary copies, try whitelisting instead of blacklisting list classes. For example:
if (list.getClass() != ArrayList.class) {
list = new ArrayList<>(list);
}
(Note: That uses getClass instead of instanceof, because instanceof would also be true for any weird subclasses of ArrayList.)
There are immutable collections in java-9, but there is still no common #Immutable annotation for example or a common marker interface that we could query to get this information.
The simplest way I can think of would be simply to get the name of the class of such an instance:
String nameList = List.of(1, 2, 3).getClass().getName();
System.out.println(nameList.contains("Immutable"));
but that still relies on internal details, since it queries the name of the common class ImmutableCollections, that is not public and obviously can change without notice.
From ImmutableList javadocs:
Unlike
Collections.unmodifiableList(java.util.List),
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. ImmutableList is convenient
for public static final lists
("constant lists") and also lets you
easily make a "defensive copy" of a
list provided to your class by a
caller.
Does it mean that:
if I have ImmutableList of Dimension objects (for example) then I can't change any Dimension object in it?
and if I have Collections.unmodifiableList (list) of Dimension objects then I can't only add or delete any object but I can change them (for example call setDimension(width, height) method)?
No, the immutability is only applied to the amount and references of the objects in the Collection, and does not address the mutability of objects you put in the Collection.
What Immutable list gains over the standard JDK Collections.unmodifiableList is that by using ImmutableList you are guaranteed that the objects referenced, their order and the size of the list cannot change from any source. With Collections.unmodifiableList if something else has a reference to the underlying list, that code can modify the list even though you have a reference to an unmodifiable list.
If, however, you want true immutability, you have to fill the list with immutable objects.
Using Collections.unmodifiableList creates a wrapper around your List. if the underlying list changes, so does your unmodifiableList's view.
As the documentation says, Google's code creates a copy. It's a more expensive computation and consumes more memory, but if someone alters the original list, it cant affect the ImmutableList.
Neither of these will prevent you from changing an object in a list, or it's fields, or fields of fields, etc.
ImmutableList is similar to Collections.unmodifiableList( new ArrayList( list ) ) . Note that the newly created ArrayList is not assigned to a field or variable.
No, the contained individual objects can still be modified. A collection is really only storing references to the contained objects, not a full copy of every object.
You can modify the list by modifying the parent Collection you called Collections.unmodifiableList(list) on. But yes, you CAN use setDimension to change a stored list element.
You might also want to take a look at this question (What's the difference between Collections.unmodifiableSet() and ImmutableSet of Guava).
I was looking around for some elegant solution to removing null values from a List. I came across the following post, which says I can use list.removeAll(Collections.singletonList(null));
This, however, throws an UnsupportedOperationException, which I'm assuming is because removeAll() is attempting to do some mutative operation on the immutable singleton collection. Is this correct?
If this is the case, what would be a typical use of this singletonList? To represent a collection of size 1 when you're sure you don't want to actually do anything with the collection?
Thanks in advance.
It works like a charm:
List<String> list = new ArrayList<String>();
list.add("abc");
list.add(null);
list.add("def");
list.removeAll(Collections.singletonList(null));
System.out.println(list); //[abc, def]
Indeed Collections.singletonList(null) is immutable (which is unfortunately hidden in Java[1]), but the exception is thrown from your list variable. Apparently it is immutable as well, like in example below:
List<String> list = Arrays.asList("abc", null, "def");
list.removeAll(Collections.singletonList(null));
This code will throw an UnsupportedOperationException. So as you can see singletonList() is useful in this case. Use it when client code expects a read-only list (it won't modify it) but you only want to pass one element in it. singletonList() is (thread-)safe (due to immutability), fast and compact.
[1] E.g. in scala there is a separete hierarchy for mutable and immutable collections and API can choose whether it accept this or the other (or both, as they have common base interfaces)
To answer your actual question :
what would be a typical use of this singletonList? To represent a collection of size 1 when you're sure you don't want to actually do anything with the collection?
The typical use is if you have one element and want to pass it to a method that accepts a List, ie
public void registerUsers(List<User> users) {...}
User currentUser = Login Manager.getCurrentUser();
registerUsers(Collections.singletonList(currentUser));
The removeAll() is a special case for this.
Has your list been protected with
Collections.unmodifiableList(list)
Because if you have protected it and try to modify it later you get that error.
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.
From ImmutableList javadocs:
Unlike
Collections.unmodifiableList(java.util.List),
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. ImmutableList is convenient
for public static final lists
("constant lists") and also lets you
easily make a "defensive copy" of a
list provided to your class by a
caller.
Does it mean that:
if I have ImmutableList of Dimension objects (for example) then I can't change any Dimension object in it?
and if I have Collections.unmodifiableList (list) of Dimension objects then I can't only add or delete any object but I can change them (for example call setDimension(width, height) method)?
No, the immutability is only applied to the amount and references of the objects in the Collection, and does not address the mutability of objects you put in the Collection.
What Immutable list gains over the standard JDK Collections.unmodifiableList is that by using ImmutableList you are guaranteed that the objects referenced, their order and the size of the list cannot change from any source. With Collections.unmodifiableList if something else has a reference to the underlying list, that code can modify the list even though you have a reference to an unmodifiable list.
If, however, you want true immutability, you have to fill the list with immutable objects.
Using Collections.unmodifiableList creates a wrapper around your List. if the underlying list changes, so does your unmodifiableList's view.
As the documentation says, Google's code creates a copy. It's a more expensive computation and consumes more memory, but if someone alters the original list, it cant affect the ImmutableList.
Neither of these will prevent you from changing an object in a list, or it's fields, or fields of fields, etc.
ImmutableList is similar to Collections.unmodifiableList( new ArrayList( list ) ) . Note that the newly created ArrayList is not assigned to a field or variable.
No, the contained individual objects can still be modified. A collection is really only storing references to the contained objects, not a full copy of every object.
You can modify the list by modifying the parent Collection you called Collections.unmodifiableList(list) on. But yes, you CAN use setDimension to change a stored list element.
You might also want to take a look at this question (What's the difference between Collections.unmodifiableSet() and ImmutableSet of Guava).