«1. Обзор

Преобразование коллекций Java из одного типа в другой — обычная задача программирования. В этом руководстве мы преобразуем коллекцию любого типа в список ArrayList.

На протяжении всего руководства мы будем предполагать, что у нас уже есть коллекция объектов Foo. Оттуда мы создадим ArrayList, используя различные подходы.

2. Определение нашего примера

Но прежде чем продолжить, давайте смоделируем наш ввод и вывод.

Нашим источником может быть коллекция любого типа, поэтому мы объявим ее с помощью интерфейса Collection:

Collection<Foo> srcCollection;

Нам нужно создать ArrayList с тем же типом элемента:

ArrayList<Foo> newList;

3. Использование Конструктор ArrayList

Самый простой способ скопировать коллекцию в новую коллекцию — использовать ее конструктор.

В нашем предыдущем руководстве по ArrayList мы узнали, что конструктор ArrayList может принимать параметр коллекции:

ArrayList<Foo> newList = new ArrayList<>(srcCollection);
    Новый ArrayList содержит поверхностную копию элементов Foo в исходной коллекции. Порядок такой же, как и в исходной коллекции.

Простота конструктора делает его отличным вариантом для большинства сценариев.

4. Использование Streams API

Теперь давайте воспользуемся преимуществами Streams API для создания ArrayList из существующей коллекции:

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

В этом фрагменте:

    Мы берем поток из источника collection и примените оператор collect() для создания списка. Мы указываем ArrayList::new, чтобы получить нужный тип списка. Этот код также создаст поверхностную копию.

Если бы нас не заботил точный тип List, мы могли бы упростить:

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

Обратите внимание, что toCollection() и toList() статически импортируются из Collectors. Чтобы узнать больше, обратитесь к нашему руководству по коллекторам Java 8.

5. Глубокое копирование

Прежде чем мы упомянули «поверхностные копии». Под этим мы подразумеваем, что элементы в новом списке — это точно такие же экземпляры Foo, которые все еще существуют в исходной коллекции. Поэтому мы скопировали Foos в новый список по ссылке.

Если мы изменим содержимое экземпляра Foo в любой из коллекций, эта модификация будет отражена в обеих коллекциях. Следовательно, если мы хотим изменить элементы в любой коллекции без изменения другой, нам нужно выполнить «глубокую копию».

Чтобы глубоко скопировать Foo, мы создаем совершенно новый экземпляр Foo для каждого элемента. Следовательно, все поля Foo необходимо скопировать в новые экземпляры.

Давайте определим наш класс Foo, чтобы он знал, как выполнять глубокое копирование самого себя:

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);
    }
}

Здесь мы видим, что поля id и name имеют тип int и String. Эти типы данных копируются по значению. Следовательно, мы можем просто назначить их обоих.

Родительское поле — это другой Foo, который является классом. Если Foo подвергнется мутации, эти изменения затронут любой код, который использует эту ссылку. Мы должны глубоко скопировать родительское поле.

Теперь мы можем вернуться к нашему преобразованию ArrayList. Нам просто нужен оператор карты, чтобы вставить глубокую копию в поток:

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

Мы можем изменить содержимое любой коллекции, не затрагивая другую.

Глубокое копирование может быть длительным процессом в зависимости от количества элементов и глубины данных. Использование параллельного потока здесь может обеспечить повышение производительности, если это необходимо.

6. Управление порядком списка

По умолчанию наш поток доставляет элементы в наш ArrayList в том же порядке, в котором они встречаются в исходной коллекции.

Если мы хотим изменить этот порядок, мы можем применить к потоку оператор sorted(). Чтобы отсортировать наши объекты Foo по имени:

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

Мы можем найти более подробную информацию о порядке потоков в этом предыдущем руководстве.

7. Заключение

Конструктор ArrayList — это эффективный способ получить содержимое коллекции в новый список ArrayList.

Однако, если нам нужно изменить полученный список, Streams API предоставляет мощный способ изменить процесс.

Код, используемый в этой статье, можно полностью найти на GitHub.