programowanieProgramista Kotlin

Wyjaśnij różnicę między shallow copy a deep copy w Kotlinie. Jak zaimplementować głębokie kopiowanie obiektów i jakie są typowe błędy przy kopiowaniu złożonych struktur?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Kotlinie, shallow copy (powierzchowne kopiowanie) tworzy nowy obiekt, ale zagnieżdżone obiekty (np. elementy kolekcji lub pola-referencje) nie są kopiowane, a odnoszą się do tych samych obiektów co oryginalny egzemplarz. Deep copy (głębokie kopiowanie) oznacza rekurencyjne kopiowanie wszystkich zagnieżdżonych obiektów (grafu obiektów), tak aby oryginał i kopia były całkowicie niezależne.

W standardowej bibliotece Kotlin nie ma uniwersalnego narzędzia do głębokiego kopiowania, więc trzeba to zaimplementować ręcznie, szczególnie dla złożonych struktur lub grafów z cyklami.

Przykład powierzchownego kopiowania:

data class Person(val name: String, val addresses: List<String>) val original = Person("Anna", listOf("Moskwa", "Londyn")) val shallow = original.copy() println(original.addresses === shallow.addresses) // true, ten sam odnośnik

Przykład głębokiego kopiowania:

data class Person(val name: String, val addresses: List<String>) { fun deepCopy(): Person = Person(name, addresses.toList()) } val deep = original.deepCopy() printfn(original.addresses === deep.addresses) // false, nowa kopia listy

Pytanie z haczykiem.

Jeśli użyjesz .copy() dla klasy data z wewnętrznymi kolekcjami, czy kolekcje zostaną skopiowane? Dlaczego?

Odpowiedź z przykładem:

Nie, copy() tworzy powierzchowną kopię. Zagnieżdżone kolekcje i obiekty pozostaną referencjami do tych samych obiektów. Jakiekolwiek zmiany w tych obiektach będą widoczne zarówno w oryginale, jak i w kopii, co może prowadzić do błędów.

data class Box(val items: MutableList<String>) val box1 = Box(mutableListOf("A")) val box2 = box1.copy() box2.items.add("B") printfn(box1.items) // [A, B] — zmodyfikowano również oryginał!

Przykłady realnych błędów z powodu nieznajomości subtelności tematu.


Historia

W projekcie przechowywano stan obiektu za pomocą zwykłego .copy() klasy data, sądząc, że uzyskuje się "zdjęcie". Przy powrocie do starego stanu okazało się, że zagnieżdżona lista (mutableList) została zmieniona we wszystkich kopiach z powodu powierzchownego kopiowania.


Historia

W systemie dokumentów kopiowano encje przez serializację/deserializację za pomocą Gson, spodziewając się głębokiego kopiowania, ale pola nullable i daty były serializowane niepoprawnie — w rezultacie pojawiały się nieprawidłowe wartości i błędy logiki biznesowej.


Historia

Przy migracji stanu w sklepie podobnym do Redux dla deweloperów Androida zaczęto używać copy() dla drzew stanu, oczekując pełnej niezależności kopii. W rezultacie jedna gałąź sklepu mogła mutować stan innej z powodu referencji do tych samych wewnętrznych kolekcji mutable.