In this quick tutorial, we'll show different ways to copy a List to another List and a common error produced in the process.
For an introduction to the use of Collections, please refer to this article here.
A simple way to copy a List is by using the constructor that takes a collection as its argument:
List<Plant> copy = new ArrayList<>(list);
Due to the fact that we're copying reference here and not cloning the objects, every amends made in one element will affect both lists.
For that reason, using the constructor is good to copy immutable objects:
List<Integer> copy = new ArrayList<>(list);
Integer is an immutable class, its value is set when the instance is created and can never change.
An Integer reference can thus be shared by multiple lists and threads and there's no way anybody can change its value.
3. List ConcurrentAccessException
A common problem working with lists is the ConcurrentAccessException. This could mean that we're modifying the list while we're trying to copy it, most likely in another thread.
To fix this issue we have to either:
- Use a designed for concurrent access collection
- Lock the collection appropriately to iterate over it
- Find a way to avoid needing to copy the original collection
Considering our last approach, it isn't thread-safe. So that if we want to resolve our problem with the first option, we may want to use CopyOnWriteArrayList, in which all mutative operations are implemented by making a fresh copy of the underlying array.
For further information, please refer to this article.
In case we want to lock the Collection, it's possible to use a lock primitive to serialize read/write access, such as ReentrantReadWriteLock.
Another approach to copy elements is using the addAll method:
List<Integer> copy = new ArrayList<>(); copy.addAll(list);
It's important to keep on mind whenever using this method that, as with the constructor, the contents of both lists will reference the same objects.
The Collections class consists exclusively of static methods that operate on or return collections.
One of them is copy, which needs a source list and a destination list at least as long as the source.
It will maintain the index of each copied element in the destination list such as the original:
List<Integer> source = Arrays.asList(1,2,3); List<Integer> dest = Arrays.asList(4,5,6); Collections.copy(dest, source);
In the above example, all the previous elements in the dest list were overwritten because both lists have the same size.
In case that the destination list size is larger than the source:
List<Integer> source = Arrays.asList(1, 2, 3); List<Integer> dest = Arrays.asList(5, 6, 7, 8, 9, 10); Collections.copy(dest, source);
Just the three first items were overwritten while the rest of the elements in the list are conserved.
6. Using Java 8
This version of Java opens our possibilities by adding new tools. The one we will explore in the next examples is Stream:
List<String> copy = list.stream() .collect(Collectors.toList());
The main advantages of this way are the opportunity to use skip and filters. In the next example we're going to skip the first element:
List<String> copy = list.stream() .skip(1) .collect(Collectors.toList());
It's possible to filter by the length of the String too or by comparing an attribute of our objects:
List<String> copy = list.stream() .filter(s -> s.length() > 10) .collect(Collectors.toList());
List<Flower> flowers = list.stream() .filter(f -> f.getPetals() > 6) .collect(Collectors.toList());
It's probable we want to work in a null-safe way:
List<Flower> flowers = Optional.ofNullable(list) .map(List::stream) .orElseGet(Stream::empty) .collect(Collectors.toList());
And skip an element using this way too:
List<Flower> flowers = Optional.ofNullable(list) .map(List::stream).orElseGet(Stream::empty) .skip(1) .collect(Collectors.toList());
7. Using Java 10
Finally, one of the last Java version allows us to create an immutable List containing the elements of the given Collection:
List<T> copy = List.copyOf(list);