1
1

Javaのcloneメソッドとシャローコピー

Posted at

はじめに

新卒1年目の時、Java Silverの問題集に掲載されていたある問題にかなり悩まされた。その際Javaの仕様とコンピュータのメモリについて勉強できたので、自分なりに記しておく。

目次

問題設定
前提知識
コピーの方法
解答
感想

問題設定

問題:A~Dのコードを実行したとき、Trueになるのはどれか

定義.java
char[][] array1 = {{'a', 'b'}, {'c', 'd'}};
char[][] array2 = array1.clone();
char[] array3 = array1[1].clone();
  • Aarray1[1] == array3
  • Barray1[1] == array3[1]
  • Carray1[1] == array2[1]
  • Darray1.equals(array2)

cloneメソッドの詳細がよくわからなくても、Bは次元が異なるので不正解(Falseになる)であることがわかる。残りの3つについて以下で見ていく。

前提知識

同一性と同値性

プログラミングを勉強し始めるとすぐにこの概念に辿り着く。

  • 同値性:変数の中の値が同じかどうか
  • 同一性:変数の参照先(アドレス)が同じかどうか

これらはよく==equalsメソッドの問題として出題されるので、仕様も含めてサンプルコードで確認する。

String str1 = "aaa";
String str2 = new String("aaa");
System.out.println(str1 == str2);   //false
System.out.println(str1.equals(str2));   //true
        
String[] arr1 = {"bbb"};
String[] arr2 = new String[]{"bbb"};
String[] arr3 = arr1;
System.out.println(arr1 == arr2);   //false
System.out.println(arr1.equals(arr2));   //false
System.out.println(arr1 == arr3);   //true
System.out.println(arr1.equals(arr3));   //true
  • str1 == str2は同一性をチェックする。str1str2はインスタンスが異なる(メモリ上のアドレスが異なる)ためFalseが返る。
  • str1.equals(str2)は同値性をチェックする。str1str2のメモリ上のアドレスは異なるが、文字列は同じであるためTrueが返る。

単純な文字列の例は簡単だが、これが配列になると少しややこしい。

  • arr1 == arr2も同一性をチェックする。str1str2はインスタンスが異なるためFalseが返る。
  • arr1.equals(arr2)equals同一性をチェックする。これは配列に用いるequalsメソッドがObjectクラスのメソッドであり、それは同一性をチェックするからだ。よってarr1arr2はインスタンスが異なるためFalseを返す。
  • arr1 == arr3はもちろんTrue
  • arr1.equals(arr3)は上で見た通り同一性をチェックする。arr1arr3は同じインスタンスだからTrueを返す。

これは二重配列になっても同じである。

String[][] arr4 = {{"a"}, {"b"}};
String[][] arr5 = new String[][]{{"a"}, {"b"}};
System.out.println(arr4 == arr5);   //false
System.out.println(arr4.equals(arr5));   //false

cloneメソッド

その名の通り、自分自身をコピーする。何をやっているかは下のコードを見ていただきたい。

String[][] arr6 = arr4.clone();
for(int i=0;i<2;i++){
    System.out.println("arr6の" + i + "0成分は" + arr6[i][0]);
}

上で定義した二重配列arr4のcloneをarr6に代入した。コピーであればfor文も実行できるはずである。実際このコードは正しく処理され、想定通りの結果が返ってくる。

arr6の00成分はa
arr6の10成分はb

さて、今度はこれらの同一性や同値性を考えてみよう。

System.out.println(arr4 == arr6);
System.out.println(arr4.equals(arr6));
System.out.println(arr4[0] == arr6[0]);
System.out.println(arr4[0].equals(arr6[0]));
false
false
true
true

この結果には当時かなり違和感を持った。arr4 == arr6という"全体"は同一でないのに、arr4[0] == arr6[0]という"部分"は同一といっているからだ。
この疑問を解消するためにはコピーのされ方ついて知っておく必要がある。

コピーの方法

ここで、コピーにはシャローコピーディープコピーの2種類があることをおさえておきたい。特に、Javaのcloneメソッドは内部でシャローコピーを行っている。

  • シャローコピー
    メモリ上にある実体はコピーせず、アドレスだけコピーしている。
  • ディープコピー
    メモリ上にある実体とそのアドレスを再帰的にコピーする。

arr4arr6を使ってシャローコピーの性質をみてみると次のようになる。

System.out.println(arr4 == arr6);   //false
System.out.println(arr4[0] == arr6[0]);   //true

通常はコピーを行うとメモリ上の別のアドレスに同じ実体が作成される。すると、==で同一性をチェックするとFalseとなりそうだ。しかし、arr4[0] == arr6[0]はそうではない。これはarr4のアドレスのみがメモリ上でコピーされ、arr4[0]の実体はコピーされていないからだ。なお、arr4[0]arr6[0]は同じ実体を参照しているため、どちらかに変更を加えるともう一方にも影響が出る。

解答

ここまでを踏まえて最初の問題に解答してみる。

  • Aarray1[1] == array3
    • array1[1] == array1[1].clone()ということであるが、clone元とそのコピーはアドレスが異なるため不正解。
  • Barray1[1] == array3[1]
    • 次元が異なるので不正解。
  • Carray1[1] == array2[1]
    • array1[1] == array1.clone()[1]であり、同じアドレスを指しているため正解となる。
  • Darray1.equals(array2)
    • 上で述べた通り、配列のequalsは同一性をチェックするため不正解。

というわけで正解はCだった。試験としてはJavaの仕様を理解していれば合格できるが、この問題はそこから少しだけはみ出た内容となっている。

感想

頭の中でメモリをイメージして、コピーの挙動を理解できれば解きやすくなると思う。この記事を書くために過去に勉強した内容を振り返っていたが、Javaの仕様だけでなくその他のコンピュータの基本部分にも興味が湧いた。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1