Java道|配列変数の代入と参照のしくみ

配列変数を代入すると、配列が増えるのではなく、同じ棚を見る名前が増える。
このしくみがわかると、Javaの参照型の考え方がぐっと見えやすくなる。

配列は、複数の値をまとめて扱える便利な仕組みです。
ここまでで、配列の宣言、要素の確保、値の代入、ループとの組み合わせなどを学んできました。

今回は、その一歩先として、配列変数そのものに、別の配列変数を代入すると何が起こるのかを見ていきます。

ここは、Javaを学び始めた人が少し不思議に感じやすいところです。
普通の int 型の変数では、代入すると値そのものがコピーされるように考えられます。
しかし、配列変数では少し動きが違います。

配列変数どうしを代入しても、配列の中身が丸ごと複製されるわけではありません。
代入された側の配列変数も、代入した側と同じ配列を指すようになります。

つまり、1つの配列を、複数の配列変数で共有する状態になります。

鬼滅の刃風にたとえると、配列は「隊士たちの補給価格が入った記録棚」です。
price1 という案内札が、その棚を指しているとします。
そこへ price2 = price1 とすると、price2 という別の案内札も、同じ記録棚を指すようになります。

このとき、記録棚が2つに増えるわけではありません。
同じ1つの棚を、price1 と price2 の2つの札から見られるようになるだけです。

この記事では、配列変数どうしの代入、同じ配列を共有するしくみ、片方から値を変更するともう片方にも影響する理由、そして参照型という考え方を、Sample5.java と Sample6.java を使って、やわらかく整理していきます。

↓クリックすると拡大表示されます。

配列変数とは何かをもう一度確認する

配列を使うときは、配列変数を用意し、その配列変数に new で作った配列を結びつけます。

たとえば、整数を3個入れられる配列を扱う場合、考え方としては次のようになります。

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

このとき、data という配列変数が、3個の要素を持つ配列を扱えるようになります。

ただし、ここで大切なのは、data という変数そのものが、3つの整数を直接持っているわけではないという点です。

配列変数は、配列本体がある場所を指す目印のようなものです。

用語役割
配列変数配列本体を指す目印
配列本体実際に値が入っている箱の集まり
要素配列本体の中にある1つ1つの箱
添字どの要素かを指定する番号

鬼滅の刃風にたとえると、配列本体は「補給記録が入った棚」です。
配列変数は、その棚へ向かうための「案内札」です。

案内札そのものに補給価格が書かれているのではありません。
案内札の先にある棚の中に、実際の価格が入っています。

このイメージがつかめると、配列変数どうしの代入がかなり理解しやすくなります。

基本型の変数と配列変数は代入の感覚が違う

int 型のような基本型の変数では、代入すると値そのものが渡されると考えるとわかりやすいです。

たとえば、a の値を b に代入したあとで a を変更しても、b の値は変わりません。

int a = 10;
int b = a;
a = 20;

この場合、b は 10 のままです。

一方、配列変数では、代入すると配列本体がコピーされるのではなく、同じ配列本体を指すようになります。

種類代入で起こること片方を変更したとき
基本型の変数値そのものが渡されるもう片方は変わらない
配列変数同じ配列を指すようになるもう片方から見ても変わる

ここが今回の一番大切なところです。

鬼滅の刃風にたとえると、基本型の代入は「訓練点数 10 と書かれた札を写して渡す」ようなものです。
一方、配列変数の代入は「同じ記録棚へ行くための案内札をもう1枚作る」ようなものです。

札に書かれた値をコピーするのか。
同じ棚への案内を共有するのか。
この違いが、基本型と配列変数の大きな違いです。

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

配列変数には、new で作った配列を代入するだけでなく、別の配列変数を代入することもできます。

たとえば、price1 がある配列を指している状態で、price2 = price1 とすると、price2 も price1 と同じ配列を指すようになります。

書き方起こること
int[] price1 = new int[3];3個の要素を持つ配列を作り、price1 が指す
int[] price2;price2 という配列変数だけを用意する
price2 = price1;price2 も price1 と同じ配列を指す

ここで大切なのは、price2 = price1 としても、配列本体がもう1つ作られるわけではないということです。

配列本体は1つです。
その1つの配列本体を、price1 と price2 の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円です。

このプログラムでは、まず price1 が3個の要素を持つ配列を指しています。

int[] price1;
price1 = new int[3];

そのあと、price1 の各要素に値を入れています。

添字
0120
1250
2380

次に、price2 という配列変数を用意します。

int[] price2;

この段階では、price2 はまだ配列本体を指していません。
そして次の文で、price2 に price1 を代入します。

price2 = price1;

これにより、price2 も price1 と同じ配列を指すようになります。

price2 = price1 で配列はコピーされない

price2 = price1 という文を見ると、price1 の中身が price2 に丸ごとコピーされるように感じるかもしれません。

でも、配列変数どうしの代入では、配列本体が複製されるわけではありません。

起きていることは、price2 も price1 と同じ配列本体を指すようになる、ということです。

考え方正しいか
price1 の配列が丸ごとコピーされて、price2 用の別配列ができる×
price1 と price2 が同じ1つの配列を指すようになる

鬼滅の刃風にたとえると、price1 が「補給価格棚」への案内札を持っているとします。
price2 = price1 は、その棚の中身を全部写して別の棚を作ることではありません。
price2 も同じ補給価格棚へ向かう案内札を持つようになる、という意味です。

だから、price1 から見ても price2 から見ても、同じ値が見えます。

図:配列変数どうしの代入は同じ配列を指す

↓クリックすると拡大表示されます。

この図が示していること

この図では、price2 = price1; によって、price1 と price2 が同じ配列を指すようになることを表しています。

右側にある配列本体は1つだけです。
その1つの配列に対して、price1 と price2 の両方から矢印が伸びています。

ここからわかることは、配列変数どうしを代入しても、配列本体が2つに増えるわけではないということです。
増えるのは、同じ配列を指す配列変数の名前です。

片方から値を変更するともう片方にも影響する

配列変数どうしを代入したあと、price1 と price2 は同じ配列を指しています。

そのため、price1 から値を変更すると、price2 から見ても変更後の値が見えます。
逆に、price2 から変更しても、price1 から見える値も変わります。

これは、price1 と price2 が別々の配列を持っているのではなく、同じ1つの配列を共有しているからです。

状態意味
price1[1] を変更する共有している配列の2番目の要素を変更する
price2[1] を見る同じ配列の2番目の要素を見る
結果price2 からも変更後の値が見える

鬼滅の刃風にたとえると、price1 の案内札から補給記録棚へ行き、2番目の価格札を999円に書き換えたとします。
そのあと price2 の案内札から同じ棚へ行っても、同じ2番目の札を見ることになります。
棚が同じなので、当然999円に変わっています。

共有された配列の変更を確認する

次は、price1 から配列の中身を変更すると、price2 から見ても変わることを確認します。

ファイル名: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 と price2 が同じ配列を指す状態になります。

price2 = price1;

そのあと、price1 から2番目の要素を変更しています。

price1[1] = 999;

ここで変更しているのは、price1 という変数そのものではありません。
price1 が指している配列本体の、添字 1 の要素です。

price2 も同じ配列本体を指しているため、price2[1] から見ても 999 になります。

price1[1] を変更すると price2[1] も変わって見える理由

Sample6.java の大切な流れを整理すると、次のようになります。

順番状態
1price1 が3個の要素を持つ配列を指す
2配列に 120、250、380 を入れる
3price2 = price1; で price2 も同じ配列を指す
4price1[1] = 999; で共有している配列の2番目を変更する
5price2[1] から見ても同じ場所なので 999 と表示される

ここで起きているのは、price1 の変更が price2 に伝わっている、というよりも、そもそも同じ場所を見ている、ということです。

見方内容
price1[1]共有している配列の添字1を見る
price2[1]同じ配列の添字1を見る
結果どちらも同じ値 999 が見える

鬼滅の刃風にたとえると、price1 と price2 は、同じ補給記録棚へ向かう2つの案内札です。
price1 の札を使って棚に行き、2番目の札を999円に書き換えると、price2 の札を使って同じ棚に行っても、同じ999円の札を見ることになります。

図:共有している配列の中身を変更すると両方に反映される

↓クリックすると拡大表示されます。

この図が示していること

この図では、price1 と price2 が同じ配列を共有している状態で、price1[1] = 999; を実行したあとの様子を表しています。

変更されたのは price1 という変数そのものではなく、price1 が指している配列本体の添字 1 の要素です。

price2 も同じ配列本体を指しているため、price2[1] から見ても同じ 999 が見えます。

ここからわかることは、配列変数どうしを代入したあとに片方から要素を変更すると、もう片方にも影響して見えるということです。
理由は、2つの配列変数が別々の配列を持っているのではなく、同じ配列を共有しているからです。

参照型という考え方

配列変数は、Javaでは参照型の変数です。

参照という言葉は、ここでは「データ本体がある場所を指している」という感覚で考えるとわかりやすいです。

種類変数が持つもの
基本型の変数値そのもの
参照型の変数データ本体の場所を示す情報

配列変数は、配列本体を直接その中に持っているのではなく、配列本体を指す情報を持っています。

だから、配列変数どうしを代入すると、値のコピーではなく、同じ配列本体を指す状態になります。

鬼滅の刃風にたとえると、参照型の変数は「記録棚そのもの」ではなく、「記録棚へ向かう案内札」です。
案内札を別の隊士に渡すと、その隊士も同じ棚へ行けるようになります。
でも、棚そのものが増えるわけではありません。

配列変数の代入で勘違いしやすいこと

配列変数の代入では、次のような誤解が起きやすいです。

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

特に大切なのは、代入しても配列本体は増えないということです。

price2 = price1;

この文は、price1 の配列を複製する文ではありません。
price2 も price1 と同じ配列を指すようにする文です。

この違いをしっかり押さえておくと、配列を使ったプログラムの動きがかなり正確に読めるようになります。

配列本体を別々にしたい場合はどう考えるか

配列変数どうしを代入すると、同じ配列を共有します。

そのため、別々の配列として扱いたい場合は、それぞれに new で別の配列を用意する必要があります。

考え方としては、次のようになります。

状態配列本体
price2 = price1;1つの配列を共有する
price1 = new int[3];
price2 = new int[3];
別々の配列を持つ

price1 と price2 を完全に別の配列として扱いたいなら、それぞれに別の配列本体を用意する必要があります。

鬼滅の刃風にたとえると、price2 = price1 は、同じ補給記録棚への案内札を共有する状態です。
別々の棚が必要なら、price1 用の棚と price2 用の棚をそれぞれ新しく作る必要があります。

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

配列変数の代入を理解すると、Javaのコードを読む力がかなり上がります。

特に、次のような場面で役立ちます。

場面役立つ理由
配列を別の変数に代入するとき同じ配列を共有していると判断できる
配列の中身がどこで変わったか調べるときどの変数から同じ配列を見ているか追える
メソッドに配列を渡す学習につながる配列が共有される感覚を理解しやすくなる
クラスやオブジェクトを学ぶ準備になる参照型の考え方に慣れられる

この内容は、配列だけで終わる話ではありません。
この先でクラスやオブジェクトを学ぶときにも、参照型の考え方が出てきます。

鬼滅の刃風にたとえると、隊士情報クラスや任務記録クラスのようなものを扱うときにも、「変数が本体そのものを持っているのか、本体への案内札を持っているのか」という考え方が大切になります。

配列変数の代入を読むときのポイント

配列変数の代入を読むときは、次の順番で確認するとわかりやすいです。

順番確認すること
1どの配列変数が new で配列本体を作っているか
2どの配列変数に、どの配列変数を代入しているか
3代入後に同じ配列を共有しているか
4どの要素が変更されているか
5変更後、ほかの配列変数からも同じ値が見えるか

Sample6.java なら、price1 が new で配列を作っています。
そのあと price2 = price1; によって、price2 も同じ配列を指すようになります。
そして price1[1] = 999; によって、共有している配列本体の添字 1 が変更されます。

この流れを追えると、なぜ price2[1] も 999 になるのかが自然に理解できます。

配列変数は同じ配列を共有することがある

今回の内容で特に大切なのは、配列変数どうしの代入では、配列の中身が丸ごとコピーされるわけではないという点です。

price2 = price1;

この文によって、price2 は price1 と同じ配列を指すようになります。

つまり、price1 と price2 は、同じ配列を共有します。

そのため、どちらか一方から配列の中身を変更すると、もう一方から見ても変更後の値が見えます。

大切なこと内容
配列変数は参照型配列本体の場所を指す
代入しても配列本体は増えない同じ配列を指す変数が増える
片方から変更するともう片方にも見える同じ配列本体を共有しているため
基本型とは代入の感覚が違う値そのものではなく参照が渡る

鬼滅の刃風にたとえると、price1 と price2 は、同じ補給記録棚へ向かう2つの案内札です。
案内札が2つあっても、棚は1つです。
棚の中身を書き換えれば、どちらの案内札から見に行っても、同じ変更後の記録を見ることになります。

この感覚をしっかり持っておくと、配列だけでなく、これから学ぶクラスやオブジェクトの理解にもつながっていきます。