Does Collections.unmodifiableCollection(list) copy the collection? - java

private List list;
If we use Collections.unmodifiableCollection(list), will this return a copy of the collection, or is it faster than creating a copy? We could do other.addAll(list) but we have list of 600,000 object, so addAll is not so good.
Caller just needs a read-only collection.

Collections.unmodifiableList just returns an unmodifiable wrapper; it does not copy the contents of the input list.
Its Javadoc states this fairly clearly:
Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists. Query operations on the returned list "read through" to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an UnsupportedOperationException.
As Matt Ball mentioned, if you don't need the internal List to be mutable, you may want to just store a Guava ImmutableList internally... you can safely give that to callers directly since it can never change.

Does Collections.unmodifiableCollection(list) copy the collection?
The other answers are correct (+1s all around): the answer is no.
Instead of Collections.unmodifiableList() you can use Guava's ImmutableList.copyOf() to create an immutable (not modifiable) list copy.

Collections.unmodifiableCollection(..) simply wraps the original collection, disabling methods for modification. It doesn't copy it.
If you change the original list, the "unmodifiable" collection will also change. But the client having only the unmodifiable collection can't change it.

Related

Returning objects from a public method

I have a method:
public List getDocuments(Long orgClientId, long userId) {
// do stuff
List list = new ArrayList();
// do stuff to fill the list. The list will be of hashmaps.
return list;
}
I don't want my list to be modified outside this method. How should I return it then? Cloning it? Or just returning a new instance like
return new ArrayList<>(list);
would do it?
You can make it superficially unmodifiable thus:
return Collections.unmodifiableList(list)
This wraps the list in order that invoking any of the mutation methods will result in an exception.
However, does it really matter to you if somebody modifies the list? You are giving back a new list instance each time this method is invoked, so there is no issue with two callers getting the same instance and having to deal with interactions between the mutations.
Whatever step you take to attempt to make it unmodifiable, I can just copy the list into a modifiable container myself.
It's also quite inconvenient to me as a caller if you give me back something which I can't detect whether it is mutable or not - it looks like a List; the only way to see if I can mutate it is by calling a mutation method. That's a runtime failure, which makes it a PITA.
Update, to add an alternative.
An alternative to Collections.unmodifiableList is something like Guava's ImmutableList: this will likely be faster than Collections.unmodifiableList since it does not simply provide an unmodifiable view of existing list by wrapping, but rather provides a custom implementation of List which forbids mutation (there is less indirection).
The advantage of this is that you can return ImmutableList rather than just the List returned by Collections.unmodifiableList: this allows you to give the caller a bit more type information that they can't mutate it. ImmutableList (and its siblings like ImmutableSet) mark the mutation methods #Deprecated so your IDE can warn you that you might be doing something iffy.
The caller might choose to disregard that information (they assign the result to a List, rather than an ImmutableList), but that's their choice.
The idea is that the object returned by umodifiableCollection can't directly be changed, but could change through other means (effectively by changing the internal collection directly).
List<String> list = new ArrayList<String>();
list.add("One");
list.add("Two");
list.add("Three");
List<String> unmodifiableList = Collections.unmodifiableList(list);
// this doesn't throw an exception since it's using the add
// method of the original List reference, which is no problem
list.add("Four");
System.out.println(unmodifiableList);
// this, however, throws an exception
unmodifiableList.add("Five");
unmodifiableList returns an unmodifiable view of the specified list.
This method allows modules to provide users with "read-only" access to
internal lists. Query operations on the returned list "read through"
to the specified list, and attempts to modify the returned list,
whether direct or via its iterator, result in an
UnsupportedOperationException. The returned list will be serializable
if the specified list is serializable.

Regarding unmodifiable collection

Consider the following code below
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> intList1=new ArrayList<Integer>();
List<Integer> intList2;
intList1.add(1);
intList1.add(2);
intList1.add(3);
intList2=Collections.unmodifiableList(intList1);
intList1.add(4);
for(int i=0;i<4;i++)
{
System.out.println(intList2.get(i));
}
}
}
The result of the above code is
1
2
3
4
In the above code we create an unmodifiable List intList2 from the contents of the List intList1. But after the Collections.unmodifiable statement when I make a change to intList1 that change reflects to intList2. How is this possible ?
You need to read the Javadoc for Collections.unmodifiableList
Returns an unmodifiable view of the specified list.
This means that the returned view is unmodifiable. If you have the original reference you can change the collection. If you change the collection then changes will be reflected in the view.
This has the advantage of being very fast, i.e. you don't need to copy the collection, but the disadvantage that you noted - the resulting collection is a view.
In order to create a truly unmodifiable collection you would need to copy then wrap:
intList2=Collections.unmodifiableList(new ArrayList<>(intList1));
This copies the contents of intList1 into another collection then wraps that collection in the unmodifiable variable. No reference to the wrapped collection exists.
This is expensive - the entire underlying datastore (an array in this case) needs to duplicated which (generally) takes O(n).
Google Guava provides immutable collections which solve some of the problems of making defensive copies:
If a collection is already immutable it is not copied again
Provide an interface which can be used to explicitly state that a collection is immutable
Provide numerous static factory methods to generate immutable collections
But speed is still the key concern when using immutable copies of collections rather than unmodifiable views.
It should be noted that the usual use for Collections.unmodifiableXXX is to return from a method, for example a getter:
public Collection<Thing> getThings() {
return Collections.unmodifiableCollection(things);
}
In this case there are two things to note:
The user of getThings cannot access things so the unmodifiability cannot be broken.
It would be very expensive to copy things each time a getter were called.
In summary the answer to your question is a little more complex than you might have expected and there are a number of aspects to consider when passing collections around in your application.
From the Javadoc of Collections.unmodifiableList:
Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists.
It prevent the returned list to be modified, but the original list itself still can be.
In your code you are
intList2=Collections.unmodifiableList(intList1);
creating unmodifiableList in intList2. So you are free to make changes in inList1
but you are not allowed to do any changes in intList2
try this:
intList2.add(4);
you will get
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.add(Unknown Source)
above exception.
Collections.unmodifiableList returns a "read-only" view of the internal list. While the object that was returned is not modifiable the original list that it references can be modified. Both objects point to the same object in memory so it will reflect changes made.
Here is a good explanation of what is happening.
That happens because the unmodifiable list is internally backed for the first list, if you really want it to be unmodifiable you shouldn't use the first List any more.

Why don't the unmodifiable methods from Collections class, create collections with new elements?

Suppose there is this code:
List<String> modifiableList = new ArrayList<String>(Arrays.asList("1","2","3"));
List<String> unmodifiableList = Collections.unmodifiableList(modifiableList);
System.out.println(unmodifiableList);
modifiableList.remove("1");
modifiableList.remove("3");
System.out.println(unmodifiableList);
it prints
[1, 2, 3]
[2]
If changing the second line to
List<String> unmodifiableList = Collections.unmodifiableList(
new ArrayList<String>(modifiableList));
it works as expected.
The question is why doesn't the UnmodifiableList inner class from Collections (and all the other unmodifiable classes) from there create a fresh copy of the original list, as does the constructor of ArrayList for example in order to really make it unmodifiable?
Edit: I understand what the code does; my question is why was it implemented this way? Why does the constructor from the UnmodifiableList (inner class from Collections) behave like the constructor of ArrayList in creating a fresh copy of the underlying array? Why a modifiable collection (ArrayList) copies the whole content while an unmodifiable collection doesn't?
Well the purpose of the methods is to create an unmodifiable view on an existing collection. That's the documented behaviour, and in many cases it's exactly what you want - it's much more efficient than copying all the data, for example... or you want to hand callers collections which will reflect any changes you want to make, but without allowing them to make changes.
If you want an immutable copy of the data (or at least the references...) then just create a copy and then create an immutable view over the top of it - just as you are.
So basically, you can easily create either a view or a copy-and-view, based on Collections.unmodifiable* themselves performing the view operation. So we have two orthogonal operations:
Create a copy (e.g. via the constructor)
Create a view (via Collections.unmodifiable*)
Those operations can be composed very easily. If Collections.unmodifiable* actually performed a "copy only" then we'd require other operations in order to just make a view. If you accept that both options are useful in different situations, making them composable gives lots of flexibility.
The reason is simple efficiency. Copying all of the elements of a collection could be very time-consuming, particularly if the collection being wrapped has some sort of magic going on like JPA lazy-loading, and requires extra memory. Wrapping the underlying collection as-is is a trivial operation that imposes no additional overhead. In the case where the developer really does want a separate copy (unmodifiable or not), it's very easy to create it explicitly. (I tend to use Guava Immutable* for this.)
Please note, that unmodifiableList returns a "unmodifiable view" of provided list. So the list itself stays at it is (it can be still modified), only its "unmodifiable view" is unmodifiable. You can think of it as of SQL tables and views --- you can run DML scripts on tables and it will be reflected on related views. As to ArrayList --- it's backed by... an array, so it's implementation feature, that it copies elements from provided source list (which doesn't have to be backed by an array actually). Does it answer your question?

Why local non-final variable affects the referenced field? Java

I have following code in my program:
...
private void generateStack() {
List<AdvertisementsModel> adsForModel = Storage.getAdsForId(model.getId());
...
adsForModel.clear();
...
Storage is static class with static fields and methods. generateStack method is in another class and in instance object. Why does adsForModel.clear(); affects the list in Storage class if asdForModel reference is not final?
Storage.getAdsForId(...) returns a copy of the reference to the same List object. So calls via this reference effect the same list. When you call Storage.getAdsForId there is no new List created - just a new reference to the same list.
Therefore it's good practise to either return explicitly ImmutableList or making a defensive copy of the list in Storage.getAdsForId(...) and returning the copy.
Be aware that you need to make a deep copy when AdvertisementsModel is mutable or you'll run into the same problem on a different level. (Otherwise you may have a list copy now but both lists still containing references to the same AdvertisementsModel objects and changing them still effect the list contents inside Storage.)
Java is pass by value (of the reference). So, if Storage.getAdsForId(model.getId()) returns a reference which is staticly stored within Storage then calling clear() on it in the instance will affect the same List within Storage as well. You could do this instead:
return new ArrayList<AdvertisementsModel>(Storage.getAdsForId(model.getId()));
to return a copy of the list instead which would avoid affecting the list within Storage. Of course, modifying an element of this list will still affect the same element present within the list in Storage. To avoid that, you'd have to deep copy each element in the list.
getAdsForId should return a copy of the list, otherwise it will return a reference and calling clear on the list will empty the original one.
If it is final, is the original list not affected? I have doubts about that... It's the same list instance. For this, I'd use either
new ArrayList<AdvertisementsModel>(Storage.getAdsForId(model.getId()));
which creates a new list instance and if possible, modified the Storage class to return an UnmofifiableList of the original list:
return Collections.unmodifiableList(adsForIdList);
I'd prefer this, as this solution does not create a new List instance with each call, it is the responsibility of the receiving code to decide if that needs to be created or not. However, in multithreaded environments, if the original list might be modified, this might result in ConcurrentModificationExceptions - so in that case it is wiser to create a "defensive copy" of the list in the getter itself. Be sure to keep in mind, that then the modifications to the copy will not affect the original list...

UnsupportedOperationException when trying to remove from the list returned by Array.asList

I am using a List to hold some data obtained by calling Array.asList() method. Then I am trying to remove an element using myList.Remove(int i) method. But while I try to do that I am getting an UnsupportedOperationException. What would be the reason for this? How should I resolve this problem?
Array.asList() wraps an array in the list interface. The list is still backed by the array. Arrays are a fixed size - they don't support adding or removing elements, so the wrapper can't either.
The docs don't make this as clear as they might, but they do say:
Returns a fixed-size list backed by the specified array.
The "fixed-size" bit should be a hint that you can't add or remove elements :)
Although there are other ways around this (other ways to create a new ArrayList from an array) without extra libraries, I'd personally recommend getting hold of the Google Collections Library (or Guava, when it's released). You can then use:
List<Integer> list = Lists.newArrayList(array);
The reason I'm suggesting this is that the GCL is a generally good thing, and well worth using.
As noted in comments, this takes a copy of the array; the list is not backed by the original array, and changes in either collection will not be seen in the other.
It's not java.util.ArrayList. Arrays.asList() returns its own List implementation (with changes "written through" to the array.).
It's a fixed-size list so it does not support removal.
You can create a real ArrayList from it:
new java.util.ArrayList<>(Arrays.asList(someArray));
It's very confusing how asList() works, I must admit.
Please read the API docs for Arrays.asList():
Returns a fixed-size list backed by the specified array. (Changes to the returned list
"write through" to the array.)
Note that Collections.remove(int) is marked in the Javadocs as an "optional operation", meaning not all Collections will support it. "fixed-size list" means you cannot change the list's size, which remove() would do. So it's not supported.
If you want to change the list generated by Arrays.asList(), just copy it, e.g. new ArrayList(Arrays.asList(...)).
The implementation you receive from asList doesn't implement a full List interface. I would transform the list to ArrayList and then do modifications on it.
See remove().
Because you get read-only list.
try
List newList = new ArrayList(myList);
use
ArrayList instead of List
List has fixed size element, List can neither addition item nor remove item

Categories