«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.