質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.37%

javaでオブジェクト型の配列をSystem.arraycopyした時の動作が理解できません

受付中

回答 4

投稿

  • 評価
  • クリップ 1
  • VIEW 1,898

TK_0831

score 6

 前提・実現したいこと

現在javaについて学習を進めているのですが、配列に対してSystem.arraycopy
を行うとDeepコピーとなり、
コピー先の配列の要素を変更しても、コピー元の要素の値が変更されることはない
という事がテキストやwebページに記載されていました。

しかし、以下のコードではコピー元の配列の値が変更されてしまっています。
これは基本型が値のコピーを行うのに対し、オブジェクト型は参照値をコピーしているため変更されたという解釈でよろしいのでしょうか?
どうかご教授の程お願い申し上げます。

 発生している問題・エラーメッセージ

array[1].num の値が20ではなく10になっている

エラーメッセージ

 該当のソースコード

public class Main { 
    public static void main(String[] args){
        Sample[] array = {
            new Sample(10),
            new Sample(20)
        };
        Sample[] array2 = new Sample[2];
        System.arraycopy(array,0,array2,0,array.length);
        array2[1].num = 10;
        System.out.println(array[1].num);  // 20ではなく10
        System.out.println(array2[1].num); // 10
    }
}

class Sample{
    int num;
    Sample(int num){
        this.num = num;
    }
}

 試したこと

public class Main { 
.    public static void main(String[] args){
.        int[] array = {
.            10,
.            20
.        };
.        int[] array2 = new int [2];
.        System.arraycopy(array,0,array2,0,array.length);
.        array2[1] = 10;
.        System.out.println(array[1]); // これは20のままでした
.    }
}

 補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+4

公式のJavadocには、特にディープコピーとは書かれていませんね。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/System.html#arraycopy-java.lang.Object-int-java.lang.Object-int-int-

配列のインスタンスに格納されているSampleインスタンスへの参照もそのままコピーされるので、arrayが格納しているSampleインスタンスへの参照がそのままarray2へコピーされます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/17 04:32

    ご回答いただきありがとうございます。
    初学者向けとされるページを読んでおりましたので、公式のJavadocは確認しておりませんでした。誤解を与える表現をしてしまい申し訳ございません。(今後は公式のページにも目を通すように致します。)

    やはりSystem.arraycopyではSampleインスタンスへの参照もそのままコピーされるのですね。
    勉強になりました。
    回答してくださった皆様本当にありがとうございました。

    キャンセル

+1

まとめ
newで格納場所を確保する
・参照型の場合はnew =で代入しても参照アドレスがコピーされるだけ
・参照型の場合はfor文などを使ってコピー


解決になるか分かりませんが自分なりにコピーするならこれが今のところ最適かなと思います。

    public static void main(String[] args) {
        Sample[] array1 = {
                new Sample(10),
                new Sample(20),
                new Sample(30)
        };

        Sample[] array2 = new Sample[array1.length];

        for(int i = 0; i < array1.length; i ++) {
            array2[i] = new Sample(array1[i].num);
        }

        array2[1].num = 99;

        System.out.println("array1[0] "+ array1[0].num); // 10
        System.out.println("array1[1] "+ array1[1].num); // 20 元のまま
        System.out.println("array1[2] "+ array1[2].num); // 30

        System.out.println();

        System.out.println("array2[0] "+ array2[0].num); // 10
        System.out.println("array2[1] "+ array2[1].num); // 99 ここだけ変更
        System.out.println("array2[2] "+ array2[2].num); // 30

    }
public class Sample {

    public int num;

    Sample(int num) {
        this.num = num;
    }

}

出力結果

array1[0] 10
array1[1] 20
array1[2] 30

array2[0] 10
array2[1] 99
array2[2] 30


【初心者が注意したい】Java配列コピーの方法まとめ

 参照型配列のコピー

使ったことのない気になった内容でしたので検索してみました。

分かりやすい記事があったのでリンク貼っておきますね。

記事の中でのサンプルコードではint[]でディープコピーの実演をされていました。

質問者さんのコードではclass Sampleをお使いのようですね。

まずはリンク先のようにint[]でお試ししてみてはどうでしょうか?

的外れな回答かもしれませんが見に来る方の参考にもなればと思いまして。


追記:
逆説的に実体がコピーされていないということはオブジェクト参照されている。
オブジェクト参照されているのでこの方法のままではコピー出来そうに無い。
別途イテレーションなどを使ってコピーし直す必要がありそうです。
イメージ説明
 シャローコピーとディープコピー


↓リンク先の説明画像です
イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/16 19:41

    代入のことをシャローコピーって解説するのは、結構多いものでしょうか?<【初心者が注意したい】Java配列コピーの方法まとめ
    言語違いますが、「代入演算子ではコピーできない」が、私の認識ですね。
    http://okakacacao.wpblog.jp/technology/javascript-array-shallow-copy

    キャンセル

  • 2018/08/16 20:57

    すみません。誤用している箇所があれば修正しますのでご指摘頂ければと思います。

    キャンセル

  • 2018/08/17 04:22

    ご丁寧な回答ありがとうございます。リンク先の内容も大変わかりやすかったです。

    キャンセル

0

以下のコードではコピー元の配列の値が変更されてしまっています。

配列の値が変わっているのではなく、配列の中のオブジェクトの値が変わっているだけです。

arraycopyがディープコピーかどうかを調べたいなら、

array2[1] = null;

などとするべきかと。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/16 15:25 編集

    認識か見解の相違のようです。
    「配列そのものを複製するだけで、中の要素までは複製しない」を私は、シャローコピーとしています。
    コピー先のオブジェクトを変更してもコピー元のオブジェクトが変更されないものをディープコピーとしています。
    追記:今回のコピーの観点は、配列そのものでなく、配列の要素の話だと思っています。
    なぜなら、array2はcloneでなくnewをしているので。

    キャンセル

  • 2018/08/16 15:37 編集

    シャローコピーは配列そのものも複製しない、ようするに array2 = array です。
    arraycopyのコピー対象は「配列」であって「配列の中身」ではないという認識です。

    momon-gaさんは、例えばSampleの中に別のオブジェクトがあったとして(さらにその中にも別オブジェクトが‥)、再帰的にコピーしないとディープコピーと呼ばないということでしょうか?

    キャンセル

  • 2018/08/16 15:46

    見解の相違なので、どちらが正しいとかは、あまり有意義な議論になりません。
    私の場合、不変オブジェクトでないオブジェクトを含む場合、その値をcloneした別オブジェクトを作成しない場合、ディープコピーとしません。

    キャンセル

0

オブジェクトのクローン
こういった方法もあるようなのでご参考までに。
記事の中のサンプルコードそのままでは動かないので確認用に作ってみました。
※Class名は適当に変えて別々のファイルに保存してください。

public class memo202 {

    public static void main(String[] args) throws CloneNotSupportedException {

        CloneTest c1 = new CloneTest(10);
        CloneTest c2 = c1.clone();

        System.out.println("c1:" + c1.num);
        System.out.println("c2:" + c2.num);
        System.out.println();

        c2.num = 77;

        System.out.println("c1:" + c1.num);
        System.out.println("c2:" + c2.num);
    }
}
public class CloneTest implements Cloneable {

    public int num;

    CloneTest(int num) {
        this.num = num;
    }
    public CloneTest clone() throws CloneNotSupportedException {
        return (CloneTest) super.clone();
    }
}

出力結果

c1:10
c2:10

c1:10
c2:77

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る