1. Overview
In this quick tutorial, we’ll explore 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.
2. Constructor
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);
Since we’re copying references here, and not cloning the objects, every amends made in one element will affect both lists.
As such, it’s good to use the constructor for copying 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 usually means 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 collection designed for concurrent access
- 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. 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.
If we want to lock the Collection, it’s possible to use a lock primitive to serialized read/write access, such as ReentrantReadWriteLock.
4. AddAll
Another approach to copying elements is using the addAll method:
List<Integer> copy = new ArrayList<>();
copy.addAll(list);
It’s important to keep in mind whenever using this method that, as with the constructor, the contents of both lists will reference the same objects.
5. Collections.copy
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 that’s 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.
If 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);
Here, just the first three items were overwritten, while the rest of the elements in the list were conserved.
6. Using Java 8
This version of Java expands our possibilities by adding new tools. The one we’ll explore in the following examples is Stream:
List<String> copy = list.stream()
.collect(Collectors.toList());
The main advantage of this option is the ability to use skip and filters. In the next example, we’ll skip the first element:
List<String> copy = list.stream()
.skip(1)
.collect(Collectors.toList());
It’s also possible to filter by the length of the String, 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());
We’ll likely want to skip an element in 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 versions allows us to create an immutable List containing the elements of the given Collection:
List<T> copy = List.copyOf(list);