Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Converting Java collections from one type to another is a common programming task. In this tutorial, we’ll convert any type of Collection to an ArrayList.

Throughout the tutorial, we’ll assume that we already have a collection of Foo objects. From there, we’ll create an ArrayList using various approaches.

2. Defining Our Example

But before continuing, let’s model our input and output.

Our source could be any type of collection so we’ll declare it using the Collection interface:

Collection<Foo> srcCollection;

We need to produce an ArrayList with the same element type:

ArrayList<Foo> newList;

3. Using the ArrayList Constructor

The simplest way to copy a collection to a new collection is using its constructor.

In our previous guide to ArrayList, we learned that the ArrayList constructor can accept a collection parameter:

ArrayList<Foo> newList = new ArrayList<>(srcCollection);
  • The new ArrayList contains a shallow copy of the Foo elements in the source collection.
  • The order is the same as one in the source collection.

The simplicity of the constructor makes it a great option in most scenarios.

4. Using the Streams API

Now, let’s take advantage of the Streams API to create an ArrayList from an existing Collection:

ArrayList<Foo> newList = srcCollection.stream().collect(toCollection(ArrayList::new));

In this snippet:

  • We take the stream from the source collection and apply the collect() operator to create a List
  • We specify ArrayList::new to get the list type we want
  • This code will also produce a shallow copy.

If we were not concerned about the exact List type, we could simplify:

List<Foo> newList = srcCollection.stream().collect(toList());

Note that toCollection() and toList() are statically imported from Collectors. To learn more, please refer to our guide on Java 8’s Collectors.

5. Deep Copy

Before we mentioned “shallow copies”. By that, we mean that the elements in the new list are exactly the same Foo instances that still exist in the source collection. Therefore, we’ve copied the Foos to the newList by reference.

If we modify the contents of a Foo instance in either collection that modification will be reflected in both collections. Hence, if we want to modify the elements in either collection without modifying the other we need to perform a “deep copy.”

To deep copy a Foo, we create a completely new Foo instance for each element. Consequently, all of the Foo fields need to be copied to the new instances.

Let’s define our Foo class so that it knows how to deep copy itself:

public class Foo {

    private int id;
    private String name;
    private Foo parent;

    public Foo(int id, String name, Foo parent) {
        this.id = id;
        this.name = name;
        this.parent = parent;
    }

    public Foo deepCopy() {
        return new Foo(
          this.id, this.name, this.parent != null ? this.parent.deepCopy() : null);
    }
}

Here we can see the fields id and name are int and String. These data types are copied by value. Hence, we can simply assign both of them.

The parent field is another Foo, which is a class. If Foo got mutated, any code that shares that reference would be affected by these changes. We have to deep copy the parent field.

Now we can get back to our ArrayList conversion. We just need the map operator to insert the deep copy into the flow:

ArrayList<Foo> newList = srcCollection.stream()
  .map(foo -> foo.deepCopy())
  .collect(toCollection(ArrayList::new));

We can modify the contents of either collection without affecting the other.

A deep copy can be a lengthy process depending on the number of elements and the depth of the data. Using a parallel stream here may provide a performance boost if needed.

6. Controlling the List Order

By default, our stream will deliver elements to our ArrayList in the same order as they are encountered in the source collection.

If we want to change that order we could apply the sorted() operator to the stream. To sort our Foo objects by name:

ArrayList<Foo> newList = srcCollection.stream()
  .sorted(Comparator.comparing(Foo::getName))
  .collect(toCollection(ArrayList::new));

We can find further details on stream ordering in this earlier tutorial.

7. Conclusion

The ArrayList constructor is an effective way to get the contents of a Collection into a new ArrayList.

However, if we need to tweak the resulting list, the Streams API provides a powerful way to modify the process.

The code used in this article can be found in its entirety over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!