Как правильно скопировать список (list) в Kotlin: клонирование, копирование и глубокое копирование
Основная проблема: ссылочная привязка
В Kotlin, как и в Java, присваивание переменной списка другой переменной не создаёт новый список — обе переменные ссылаются на один и тот же объект в памяти. Поэтому любые изменения через одну ссылку видны через другую.
Стандартные способы копирования списка
Kotlin предоставляет несколько встроенных методов для создания поверхностной копии списка (shallow copy). Вот самые распространённые:
.toList()— создаёт новую неизменяемую копию списка (возвращаетList).toMutableList()— создаёт новую изменяемую копиюArrayList(originalList)— конструктор, как в Java.map { it }— преобразование через map (тоже создаёт новую коллекцию)
Пример: поверхностное копирование
fun main() {
val original = mutableListOf(1, 2, 3, 4, 5)
// Вариант 1: toList() — создаёт неизменяемую копию
val copy1: List<Int> = original.toList()
// Вариант 2: toMutableList() — создаёт изменяемую копию
val copy2: MutableList<Int> = original.toMutableList()
// Вариант 3: через конструктор ArrayList
val copy3 = ArrayList(original)
// Вариант 4: через map
val copy4 = original.map { it }
copy2.add(6)
println("Original: $original") // [1, 2, 3, 4, 5] — не изменился
println("Copy2: $copy2") // [1, 2, 3, 4, 5, 6]
}
Важно: Все эти методы создают поверхностные копии. Если в списке лежат объекты (не примитивы), то копируются только ссылки на эти объекты. Сами объекты не дублируются.
Глубокое копирование (deep copy)
Если ваш список содержит изменяемые объекты (например, data class с полями), и вы хотите, чтобы копия была полностью независимой, нужно выполнить глубокое копирование. Для этого:
- Используйте
.map { it.copy() }дляdata class(у них есть встроенный метод.copy()). - Для обычных классов — реализуйте собственный механизм копирования (например, через конструктор копирования или сериализацию).
Пример с data class:
data class Person(val name: String, var age: Int)
fun main() {
val people = listOf(
Person("Alice", 25),
Person("Bob", 30)
)
// Поверхностная копия — объекты общие
val shallowCopy = people.toList()
shallowCopy[0].age = 99
println("Original: $people") // [Person(name=Alice, age=99), ...] — изменился!
// Глубокая копия — каждый объект скопирован
val deepCopy = people.map { it.copy() }
deepCopy[0].age = 10
println("Original: $people") // [Person(name=Alice, age=99), ...] — не изменился
println("DeepCopy: $deepCopy") // [Person(name=Alice, age=10), ...]
}
Дополнительные советы
- Для неизменяемых списков (
listOf) копирование обычно не нужно — они и так не меняются. - Если вам нужна только неизменяемая копия, используйте
.toList()— она возвращаетList, но сам список неизменяемый. - Для больших списков с простыми типами данных
.toMutableList()иArrayList()работают одинаково эффективно. - Метод
.clone()в Kotlin для списков не рекомендуется — он определён только для массивов (Array).
Итоговый вывод
Для копирования списка в Kotlin используйте .toList() или .toMutableList() — это идиоматичный и простой способ. Если внутри списка лежат изменяемые объекты, обязательно делайте глубокое копирование через map { it.copy() } (для data class) или аналогичный механизм.
Альтернативный подход: использование расширений и фильтрации
Иногда вам нужно не просто скопировать список, но и отфильтровать или изменить элементы в процессе копирования. В Kotlin для этого удобно использовать функции filter, mapNotNull и другие.
Пример: копирование с фильтрацией
val numbers = listOf(1, 2, 3, 4, 5)
// Копируем только чётные числа
val evenNumbers = numbers.filter { it % 2 == 0 }.toList()
println(evenNumbers) // [2, 4]
Пример: копирование с преобразованием
val words = listOf("kotlin", "java", "python")
// Копируем с изменением регистра
val upperWords = words.map { it.uppercase() }.toList()
println(upperWords) // [KOTLIN, JAVA, PYTHON]
Использование Sequence для больших списков
Если вы работаете с очень большими списками (миллионы элементов), и копирование занимает много памяти, используйте Sequence:
val largeList = (1..10_000_000).toList()
// Ленивое копирование — элементы обрабатываются по мере необходимости
val copySequence = largeList.asSequence().toList()
Однако помните: Sequence не ускоряет само копирование, но может быть полезен, если вы сразу применяете несколько операций (filter, map) — они будут выполнены за один проход.
Когда использовать эти подходы?
- Если нужно просто скопировать список — используйте
toList()илиtoMutableList(). - Если нужно скопировать и сразу модифицировать — комбинируйте с
map,filterи т.д. - Если нужна максимальная производительность при множественных операциях — рассмотрите
Sequence.
Ответов пока нет.
Войдите чтобы оставить ответ