はじめに
オブジェクト指向プログラミングでは、「コピー(複製)」 がよく使われます。
特に Prototype パターン や copyWith
/ copy()
のような API を使うときに重要になるのが、
- 浅いコピー(Shallow Copy)
- 深いコピー(Deep Copy)
です。
この記事ではその違いを整理し、Flutter(Dart)と Android(Kotlin)の具体例で比較します。
1. 浅いコピー(Shallow Copy)
定義
- プリミティブ型(int, double, Stringなど) はそのままコピーされる
- 参照型(List, Map, クラスなど) は「参照だけコピー」される
- コピー先とコピー元が 同じインスタンスを共有している状態
特徴
- 高速・軽量
- だが 独立性がない(片方を変更するともう片方に影響する)
Dart の例
class Engine {
String type;
Engine(this.type);
}
class Car {
String name;
Engine engine;
Car(this.name, this.engine);
// 浅いコピー
Car shallowCopy() => Car(name, engine);
}
void main() {
var car1 = Car("Car1", Engine("V8"));
var car2 = car1.shallowCopy();
car2.engine.type = "V6";
print(car1.engine.type); // V6 ← car1 も影響!
}
3. 深いコピー(Deep Copy)
定義
- プリミティブ型はコピーされる
- 参照型も 新しいインスタンスを再帰的に生成してコピー
- コピー先とコピー元は 完全に独立したオブジェクト
特徴
- 独立性が高い(片方を変更してももう片方に影響しない)
- だが コピーコストが高い(処理が重い)
Dart の例
class Car {
String name;
Engine engine;
Car(this.name, this.engine);
// 深いコピー
Car deepCopy() => Car(name, Engine(engine.type));
}
void main() {
var car1 = Car("Car1", Engine("V8"));
var car2 = car1.deepCopy();
car2.engine.type = "V6";
print(car1.engine.type); // V8 ← car1 は独立!
}
4. Kotlin の例
浅いコピー(デフォルトの copy()
)
data class Engine(var type: String)
data class Car(val name: String, val engine: Engine)
fun main() {
val car1 = Car("Car1", Engine("V8"))
val car2 = car1.copy() // 浅いコピー
car2.engine.type = "V6"
println(car1.engine.type) // V6 ← 影響あり!
}
深いコピー(手動実装)
fun Car.deepCopy(): Car = Car(name, Engine(engine.type))
fun main() {
val car1 = Car("Car1", Engine("V8"))
val car2 = car1.deepCopy()
car2.engine.type = "V6"
println(car1.engine.type) // V8 ← 独立
}
5. 違いまとめ(早見表)
項目 | 浅いコピー (Shallow) | 深いコピー (Deep) |
---|---|---|
プリミティブ型 | 値をコピー | 値をコピー |
参照型 | 参照だけコピー(共有) | 新しいインスタンスを生成 |
独立性 | 低い(影響し合う) | 高い(完全に独立) |
パフォーマンス | 高速・軽量 | 遅い・重い |
典型ユースケース | Immutable オブジェクト前提 | 複雑なネスト構造あり |
6. 実務での考え方
Flutter / Dart
-
copyWith
を多用(TextStyle
,ThemeData
など) - Immutable 設計なら浅いコピーで十分安全
- ネストが深い場合は
freezed
やjson_serializable
で deep copy 生成 をサポート
Android / Kotlin
-
data class.copy()
は浅いコピー(ネスト参照は共有される) - 深いコピーが必要なら
deepCopy()
を実装 - Immutable モデルを前提にして「浅いコピー+差し替え」で設計するのが一般的
まとめ
- 浅いコピー:高速・軽量だが参照を共有するため副作用に注意
- 深いコピー:完全に独立するがコストが高い
- 実務では Immutable オブジェクト設計と組み合わせ、浅いコピーをベースにすることが多い
- ネスト構造やミュータブル設計が必要な場合は、深いコピーを導入する