今や、クラスを使うプログラミングは主流の一つになっています。
JavaやC++はオブジェクト指向言語のメジャーですが、それまでそうじゃなかったPHPが採用されて久しいほど浸透しています。
そこで出てくるのがシャローコピーとディープコピーという言葉です。
ディープコピーは正真正銘のコピー
変数を代入した変数や、関数で渡されたパラメータの変数の値を変更しても、元の変数値は変わりません。
普通はそう思いますよね?
このコピーのことをディープコピーと言います。
JavaやC++などのオブジェクト指向言語がメジャーになる前は、ディープコピーという言葉はあまり見かけませんでした。
オブジェクト指向言語のコピーはシャローコピーが標準的でディープコピーの場合は、オブジェクトのclone()をコールしないといけないなどの作業が必要です。
また、int, char などのプリミティブな型の変数代入(=)はディープコピーです。これからも、元をたどればディープコピーがいわゆるデータコピー(変数代入)だということが分かります。
(プリミティブ型の代入はJavaなどのオブジェクト指向言語でも同じディープコピー。)
シャローコピーは『箱』のコピー。データコピーと思ったら痛い目にあう。
shallow は『浅い、軽薄』という意味です。シャローコピーをそのまま直訳すると『薄いコピー』。
コピーに薄いも深いもあるのか? と思いますが、そのツッコミは正しいです。
シャローコピーはデータのコピーではありません。別の変数の箱を用意するイメージ。
オブジェクト指向言語がメジャーになるまでは、シャローコピーこそ何かしらプラスアルファの作業が必要でした。
C言語のポインタ変数なんかもそう。
ポインタのことをシャロコピーと言わなくもなかったんですが、そこまで使われていたわけではなく、シャローコピーを一般的なワードにしたのはやっぱりオブジェクト指向言語。
Java, C++ などがメジャーになるとシャローコピーもよく見かけるようになります。クラスオブジェクトの変数代入(=)こそがシャローコピーだから。
『オブジェクトの代入はシャローコピー』は、オブジェクト指向言語は分かりにくいと言われる原因にもなっている。
シャローコピーは、代入元の変数値を変えたつもりはないのに変わってしまう。
シャローコピーとディープコピーのサンプルプログラム(Java)
最後に、オブジェクト指向言語がどれだけシャローコピーにこだわってるか見てみましょう。
import java.util.*;
import java.lang.*;
import java.io.*;
class Test {
public String key = "default";
}
class TestCL implements Cloneable {
public String key = "default";
@Override
public TestCL clone() throws CloneNotSupportedException {
TestCL clone = (TestCL)super.clone();
// ここにcloneへのディープコピーの処理を書く。
return clone;
}
}
class Main
{
public static void main (String[] args) throws java.lang.Exception
{
// オブジェクト型変数
Test test1 = new Test();
Test test2 = test1;
test1.key = "change";
// シャローコピーなので変更内容はすべてに反映される。
System.out.println(test1.key);
System.out.println(test2.key);
// プリミティブ型変数
int test3 = 1;
int test4 = test3;
test4 = 2;
// ディープコピーなので変更した変数だけが反映される。
System.out.println(test3);
System.out.println(test4);
// オブジェクト型のディープコピー
TestCL test5 = new TestCL();
TestCL test6 = test5.clone();
test6.key = "change deep";
// ディープコピーなので変更した変数のオブジェクトだけが反映される。
System.out.println(test5.key);
System.out.println(test6.key);
}
}
change
change
1
2
default
change deep
TestCLクラスを見て下さい。オブジェクト型のディープコピーをするために、clone()を作るために結構面倒なことしてますね?
それだけオブジェクト指向言語では、シャローコピー前提に設計されてます。
ディープコピーしたければ、面倒なことしてそれを明示しなってこと。
C言語の経験者は、ポインタの数珠つなぎで構造体を構成するデータを思い浮かべると、その面倒くささが痛いほど分かる。
ポインタつなぎのディープコピーの処理をJavaではclone()に記述する決まりになっている。
PHPなど、オブジェクト指向を後追いで追加したプログラム言語なんかは要注意。
初心者はクラスを使わずにプログラミングすることが多いので、"=" を使ったオブジェクトの代入とほかの型の代入の区別がつかなかったりします。
(同じイコール(=)でもシャローとディープで内部の動きはまったくちがう。)
プリミティブ型とオブジェクト型の "=" を使った代入はまったくちがう。
プリミティブ型 | ディープコピー。 代入元と代入先の変数は独立している。 |
オブジェクト型 | シャローコピー。 1つのデータで複数の変数を持つ。 ある変数で値を変えると、データを共有 しているすべての変数に反映される。 |
shallow の対義語の deep を使ってこれまでのコピーをディープコピーと呼ぶ。
また、ディープコピーのことをクローン(clone)と表現するプログラム言語は多く、メソッド名などに使われる。
単なる代入はシャローコピーとは言わないですよ。
シャローコピーは新しいオブジェクトを作って1階層だけコピーします。
https://en.wikipedia.org/wiki/Object_copying#Shallow_copy
> One method of copying an object is the shallow copy. In that case a new object B is created, and the fields values of A are copied over to B.
https://developer.mozilla.org/ja/docs/Glossary/Shallow_copy
> copy[0] = {"list":["oil","flour"]} とすると、元のオブジェクト内の対応する要素は **変化しません**。
JavaScriptの配列なのでPHPとは動作がことなりますが、JavaScriptでの単なる代入では **変化する** ので、シャローコピーの動作と違います。