1. Обзор
Преобразование коллекций Java из одного типа в другой — обычная задача программирования. В этом руководстве мы преобразуем любой тип Collection
в 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. Использование потокового API
Теперь давайте воспользуемся Streams API для создания ArrayList из существующей коллекции : ``
ArrayList<Foo> newList = srcCollection.stream().collect(toCollection(ArrayList::new));
В этом фрагменте:
- Мы берем поток из исходной коллекции и применяем оператор
collect()
для созданиясписка .
- Мы указываем
ArrayList::new
, чтобы получить нужный тип списка. - Этот код также создаст неглубокую копию.
Если бы нас не заботил точный тип списка
, мы могли бы упростить:
List<Foo> newList = srcCollection.stream().collect(toList());
Обратите внимание, что toCollection()
и toList()
статически импортируются из Collectors
. Чтобы узнать больше, обратитесь к нашему руководству по коллекторам Java 8 .
5. Глубокое копирование
Прежде чем мы упомянули «мелкие копии». Под этим мы подразумеваем, что элементы в новом списке — это точно такие же экземпляры Foo
, которые все еще существуют в исходной коллекции. Поэтому мы скопировали Foo
в newList
по ссылке.
Если мы изменим содержимое экземпляра 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 .