Java入門|配列変数の代入とデータの共有

配列変数の代入を理解すると、データ共有のしくみがすっきり見えてくる

ここまでで、配列は複数の値をまとめて扱える便利な仕組みだとわかってきましたね。今回はその一歩先として、配列変数そのものに別の配列変数を代入するとどうなるのかを見ていきます。

この内容は、最初は少し不思議に感じやすいところです。というのも、int型のような基本型の変数では、代入すると値そのものがコピーされる感覚で理解しやすいのですが、配列変数では少し様子が違うからです。配列変数どうしの代入では、配列の中身が丸ごと別に増えるのではなく、同じ配列を複数の変数名で共有する状態が起こります。これは、配列変数が配列そのものではなく、配列のある場所を表しているためです。

ここでは、配列変数に別の配列変数を代入したときの動き、1つの変数から値を変更するともう片方にも影響する理由、そして配列変数が参照型であることの意味まで、やさしく整理しながら見ていきましょう。

配列変数とは何かをもう一度確認しよう

配列を使うときには、まず配列変数を用意して、そこに new で作成した配列を代入します。たとえば、次のような形でしたね。これは、配列を扱うための配列変数を用意して、その先に3個分の要素をもつ配列を準備している形です。

int[] data;
data = new int[3];

このとき大切なのは、配列変数 data が、値そのものを直接3つ持っているわけではないという点です。配列変数は、配列がメモリ上のどこにあるかを表す目印のような役割を持っています。

この考え方がわかると、配列変数への代入の意味がかなり理解しやすくなります。

配列変数どうしは代入できる

配列変数には、new を使って新しい配列を代入するだけでなく、別の配列変数を代入することもできます。1つ目の配列変数に配列を用意し、2つ目の配列変数へ代入すると、2つ目の配列変数からも同じ内容が見えるようになります。

まずは、そのイメージをつかみやすいように、別のシンプルな例に置き換えて見てみましょう。

ファイル名:Sample5.java

class Sample5
{
    public static void main(String[] args)
    {
        int[] price1;
        price1 = new int[3];   // 配列を準備する

        System.out.println("price1を用意しました。");
        System.out.println("3個分の商品価格を入れられるようにしました。");

        price1[0] = 120;
        price1[1] = 250;
        price1[2] = 380;   // 配列に価格を代入する

        int[] price2;   // 配列変数だけを用意する
        System.out.println("price2を用意しました。");

        price2 = price1;   // 配列変数どうしを代入する
        System.out.println("price2にprice1を代入しました。");

        for(int i = 0; i < 3; i++){
            System.out.println("price1がさす" + (i + 1) +
                               "番目の価格は" + price1[i] + "円です。");
        }

        for(int i = 0; i < 3; i++){
            System.out.println("price2がさす" + (i + 1) +
                               "番目の価格は" + price2[i] + "円です。");
        }
    }
}

実行結果

price1を用意しました。
3個分の商品価格を入れられるようにしました。
price2を用意しました。
price2にprice1を代入しました。
price1がさす1番目の価格は120円です。
price1がさす2番目の価格は250円です。
price1がさす3番目の価格は380円です。
price2がさす1番目の価格は120円です。
price2がさす2番目の価格は250円です。
price2がさす3番目の価格は380円です。

このように、price2 に price1 を代入すると、price2 からも同じ値が見えるようになります。配列変数には別の配列変数を代入でき、代入後は同じ内容を参照できます。

ここで起きていることはコピーではない

ここがいちばん大事なポイントです。
price2 = price1 と書いたとき、配列の中身が丸ごと複製されて、まったく別の同じ配列が新しくできたわけではありません。

これは「同じ配列が2つ存在するようになることではない」ということです。左辺の配列変数が、右辺の配列変数の指している配列を指すようになる、というのが本当の意味です。

これを表にすると、次のように整理できます。

書き方起きること
int[] a = new int[3];新しい配列が作られ、a がそれを指す
int[] b;b という配列変数だけを用意する
b = a;b も a と同じ配列を指すようになる

つまり、a と b は「別々の配列」ではなく、同じ1つの配列を共有して見ている状態です。

この図では、最初は price1 だけが配列につながっています。
そのあとで price2 = price1 を行うと、price2 も同じ配列につながります。ここで大切なのは、右側の配列が2つに増えていないことです。配列は1つのままで、そこを2つの配列変数が見ているのです。

片方から値を変更すると、もう片方から見ても変わる

もし本当に別々の配列が2つ存在しているなら、片方を変更してももう片方には影響しないはずです。
でも実際には、配列変数どうしを代入したあとで片方から要素を書き換えると、もう片方から見た値も変わります。

これは、2つの配列変数が別の配列を持っているのではなく、同じ配列を共有している証拠です。

この動きを、別の例で確認してみましょう。

ファイル名:Sample6.java

class Sample6
{
    public static void main(String[] args)
    {
        int[] price1;
        price1 = new int[3];
        System.out.println("price1を用意しました。");
        System.out.println("3個分の価格を入れられるようにしました。");

        price1[0] = 120;
        price1[1] = 250;
        price1[2] = 380;

        int[] price2;
        System.out.println("price2を用意しました。");

        price2 = price1;   // 同じ配列を共有する
        System.out.println("price2にprice1を代入しました。");

        for(int i = 0; i < 3; i++){
            System.out.println("price1がさす" + (i + 1) +
                               "番目の価格は" + price1[i] + "円です。");
        }

        for(int i = 0; i < 3; i++){
            System.out.println("price2がさす" + (i + 1) +
                               "番目の価格は" + price2[i] + "円です。");
        }

        price1[1] = 999;   // 2番目の価格を変更する
        System.out.println("price1がさす2番目の価格を変更しました。");

        for(int i = 0; i < 3; i++){
            System.out.println("price1がさす" + (i + 1) +
                               "番目の価格は" + price1[i] + "円です。");
        }

        for(int i = 0; i < 3; i++){
            System.out.println("price2がさす" + (i + 1) +
                               "番目の価格は" + price2[i] + "円です。");
        }
    }
}

実行結果

price1を用意しました。
3個分の価格を入れられるようにしました。
price2を用意しました。
price2にprice1を代入しました。
price1がさす1番目の価格は120円です。
price1がさす2番目の価格は250円です。
price1がさす3番目の価格は380円です。
price2がさす1番目の価格は120円です。
price2がさす2番目の価格は250円です。
price2がさす3番目の価格は380円です。
price1がさす2番目の価格を変更しました。
price1がさす1番目の価格は120円です。
price1がさす2番目の価格は999円です。
price1がさす3番目の価格は380円です。
price2がさす1番目の価格は120円です。
price2がさす2番目の価格は999円です。
price2がさす3番目の価格は380円です。

ここでは price1[1] を 999 に変更しましたが、price2[1] も同じく 999 と表示されます。これは、両方が同じ配列を見ているからです。

なぜこんなことが起こるのか

この現象をしっかり理解するには、配列変数は配列そのものではなく、配列の場所を表すという考え方が重要です。基本型の変数は値を直接表しますが、配列変数は配列の存在場所を表すのです。

たとえば、基本型の変数ならこんなイメージです。

int a = 10;
int b = a;

この場合、b に a を代入すると、10 という値が b に入ります。
そのあと a を変えても b は変わりません。

一方で、配列変数はこうです。

int[] a = {10, 20, 30};
int[] b = a;

この場合、b は a と同じ配列の場所を共有するようになります。
そのため、a[0] を変えると b[0] から見ても変わります。

この違いを表にすると、次のようになります。

種類代入のイメージ片方を変更したとき
基本型値を渡すもう片方は変わらない
配列変数配列の場所を共有するもう片方から見ても変わる

参照型という言葉の意味

配列変数は、Javaでは参照型の変数に分類されます。同じ仲間として、配列変数のほかに、この章の後で学ぶ第8章や第10章で学ぶクラス型の変数や、第12章のインターフェイス型の変数があります。

この「参照」という言葉は、ここでは「どこにあるかを指している」という感覚でとらえるとわかりやすいです。

  • 基本型の変数
    値そのものを持つ
  • 参照型の変数
    データ本体がある場所を指す

配列変数はまさに後者です。
だからこそ、代入すると「同じ配列を見る仲間」が増えるような状態になるわけです。

勘違いしやすいポイントを整理しよう

このテーマは、初心者のうちは勘違いしやすいところがいくつかあります。
そこで、よくある誤解を表で整理しておきましょう。

よくある誤解実際はどうか
配列変数に代入すると配列が複製される複製ではなく、同じ配列を共有する
price1 と price2 は別々の配列になる同じ1つの配列を指している
price1 だけを変更すれば price2 には影響しない同じ配列なので price2 から見ても変わる
配列変数は int型の変数と同じ感覚で扱える配列変数は参照型なので動きが異なる

このあたりをしっかり整理しておくと、今後クラスやオブジェクトを学ぶときにもとても役立ちます。

配列変数の代入を理解すると何がうれしいのか

この仕組みがわかると、Javaのコードを読む力がかなり上がります。
なぜなら、配列をメソッドに渡したときや、別の変数に入れ直したときに、どこでデータが共有されているかを見抜けるようになるからです。メソッドについては、8章で学びます。

たとえば、あるメソッドに配列を渡して中身を変更すると、呼び出し元の配列も変わることがあります。これも、配列が参照型であり、同じ配列を共有しているからです。今回の内容は、その土台になる考え方です。

この図では、price1 と price2 の両方が同じ配列へつながっているため、price1 側から2番目の値を 999 に変えると、price2 側から見てもその値が 999 になっていることを表しています。
ここで変わっているのは配列変数ではなく、共有している配列の中身です。だから、どちらの変数から見ても同じ変更結果になるのです。

配列変数の代入は「同じ配列を共有する」と考えよう

今回のテーマでいちばん大切なのは、配列変数どうしの代入は、配列の中身を丸ごとコピーすることではない、という点です。
代入された側の配列変数は、代入した側と同じ配列を指すようになります。つまり、データを共有する関係になるということです。

この考え方をしっかり持っておくと、

  • なぜ両方から同じ値が見えるのか
  • なぜ片方の変更がもう片方にも反映されるのか
  • なぜ配列変数は参照型と呼ばれるのか

が、すべて自然につながって理解できるようになります。

配列の学習では、要素の扱い方だけでなく、配列変数の動きもとても大事です。このしくみがわかると、Javaの配列をより正確に、そして安心して使えるようになります。