(本記事ではJava SE 8以上を想定しています)
ディープコピーとは、コピー元/先を変更してもコピー先/元に影響がないコピーのことです。
Javaの参照型をディープコピーするには、コピー元と同じ値を持った新しいオブジェクトを生成する必要があり、少し工夫が必要です。
実際のコード例(リストの場合)
例えばリストをディープコピーしたいなら下記のようになります。もちろん他にいくらでも方法はあります。
// コピー元
List<String> listX = new ArrayList<String>();
listX.add("foo");
// 最もシンプルな例
List<String> listA = new ArrayList<String>(listX);
// NPEを避け、コピー元がnullなら空のリストを代入する例
List<String> listB = Optional.ofNullable(listX)
.map(e -> new ArrayList<>(e))
.orElseGet(() -> new ArrayList<>());
ArrayListのコンストラクタはArrayList型の引数を受け取って、新しいArrayListを生成してくれるのでこれが成り立ちます。
普通に「=」で代入(assign)を行うとどうなるか
プリミティブ型なら直感に沿った動きをします。
文字通り生身で原始的な値を直接やりとりします。これなら自然とディープコピーされますね。
int a = 123;
int b = a;
b = 456; // bに代入を行なっても a == 123 のまま
一方、参照型(の変数)はオブジェクトへの「参照」を持つため、普通に「=」すると同じ参照を共有する状態になってしまいます1。
// コピー元
MyClass obj1 = new MyClass();
obj1.setNum(20);
// ディープコピー(新しいオブジェクトを生成して中身のプリミティブ型をコピーする)
MyClass obj2 = new MyClass(); // 新しいオブジェクトを生成している
obj2.setNum(obj1.getNum()); // プリミティブ型(int)が渡される
// 参照を共有している状態
MyClass obj99 = obj1; // これだと生成されるのは「obj1と同じオブジェクトへの参照」を持った「違う変数」
String型だけ少し特殊
String2については
A String object has a constant (unchanging) value.
String literals (§3.10.5) are references to instances of class String.
The string concatenation operator + (§15.18.1) implicitly creates a new String object when the result is not a constant expression (§15.28).
とのことで、何気なく使っている文字列リテラル(String foo = "foo!";
の"foo!"
)はプリミティブ型ではなく、暗黙のうちに生成されたString型インスタンスへの「参照」です。
足し算のようなことができるが、実際は新しいオブジェクトを生成してそいつへの「参照」を返してくれています。
ちなみに、同じ文字列のリテラルは同じStringオブジェクトを参照します3 4。
String hello = "Hello"
String lo = "lo";
hello == "Hello" // true(同じ文字列、同じ参照)
hello == ("Hel"+"lo") // true(コンパイル時に結局 "Hello"になるので)
hello == ("Hel"+lo) // false(文字列結合によって新しいオブジェクトへの参照になってしまった)