Java超|配列変数の代入と参照

配列変数を代入しても、配列本体が増えるわけではない。
同じ記録パネルを見る名前が増えるだけだと分かれば、Javaの参照型の考え方がぐっと見えやすくなる。

配列は、同じ型の値をまとめて扱える便利な仕組みです。

ここまでで、配列の宣言、要素の確保、値の代入、初期化、そしてループとの組み合わせを学んできました。

たとえば、次のように配列を用意すると、3つの整数をまとめて扱えます。

int[] price1 = new int[3];

この price1 は、3個の整数を入れられる配列を扱うための配列変数です。

ただし、ここで大切なのは、price1 という変数そのものの中に、3つの値が直接入っているわけではないという点です。

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

ドラゴンボール風にたとえると、配列本体は「補給品価格が入った訓練記録パネル」です。
price1 は、その記録パネルへ向かうための案内札です。

案内札そのものに価格が書かれているわけではありません。
案内札の先にある記録パネルの中に、実際の価格が入っています。

今回学ぶ「配列変数の代入と参照」では、配列変数に別の配列変数を代入すると何が起こるのかを見ていきます。

ここは、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 型のような基本型の変数では、代入すると値そのものが渡されると考えると分かりやすいです。

たとえば、次のコードを見てみましょう。

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

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

a の値を b に代入したあとで a を 20 に変更しても、b の中には 10 が残っています。

変数値
a20
b10

基本型では、値そのものを別の変数に渡すようなイメージです。

一方、配列変数では、代入すると配列本体がコピーされるのではありません。

同じ配列本体を指すようになります。

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

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

ドラゴンボール風にたとえると、基本型の代入は「戦闘力 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つの配列変数が指すようになります。

ドラゴンボール風にたとえると、price1 は「補給品価格パネル」への案内札です。

price2 = price1 と書くと、price2 も同じ補給品価格パネルへ向かう案内札になります。

案内札は2つになります。
でも、記録パネル本体は1つのままです。

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

この図が示していること

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

右側にある配列本体は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 と同じ配列を指すようになります。

そのため、price1 から見ても、price2 から見ても、同じ値が表示されます。

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

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

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

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

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

ドラゴンボール風にたとえると、price1 が「補給品価格パネル」への案内札を持っているとします。

price2 = price1 は、そのパネルの中身を全部写して別のパネルを作ることではありません。

price2 も同じ補給品価格パネルへ向かう案内札を持つようになる、という意味です。

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

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

配列変数どうしを代入したあと、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 は別々の配列を指します。

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

この場合、price1 用の配列本体と、price2 用の配列本体が別々に作られます。

ドラゴンボール風にたとえると、price2 = price1 は、同じ補給品価格パネルへの案内札を共有する状態です。

別々のパネルが必要なら、price1 用のパネルと price2 用のパネルをそれぞれ新しく作る必要があります。

ただし、別々の配列を作っただけでは、中身まで同じになるわけではありません。

同じ値を入れたいなら、それぞれの要素に値を入れる処理が必要です。

図:共有する配列と別々の配列の違い

この図が示していること

この図では、配列を共有する場合と、別々の配列を持つ場合の違いを示しています。

左側では、price2 = price1; によって、price1 と price2 が同じ配列本体を指しています。
この場合、配列本体は1つだけです。

右側では、price1 と price2 がそれぞれ new で別々の配列本体を作っています。
この場合、配列本体は2つあります。

ここから分かることは、配列変数どうしの代入は配列本体を複製する処理ではなく、同じ配列を共有する処理だということです。

別々の配列として扱いたいなら、それぞれ new で配列本体を作る必要があります。

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

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

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

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

この内容は、配列だけで終わる話ではありません。

この先でクラスやオブジェクトを学ぶときにも、参照型の考え方が出てきます。

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

配列変数の代入は、その参照型の考え方に慣れるためのよい練習になります。

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

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

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

Sample6.java なら、price1 が new で配列を作っています。

そのあと price2 = price1; によって、price2 も同じ配列を指すようになります。

そして price1[1] = 999; によって、共有している配列本体の添字 1 が変更されます。

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

ドラゴンボール風にたとえると、読むときは次のように確認します。

確認することイメージ
どの案内札が最初にパネルを作ったかprice1 が補給品価格パネルを作る
どの案内札が同じパネルを指したかprice2 も同じパネルを見る
どの記録枠を書き換えたか添字1の価格札を999円にする
どの案内札から見ても同じかprice1 からも price2 からも999円に見える

この見方を持っておくと、参照型のコードを落ち着いて追いやすくなります。

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

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

price2 = price1;

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

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

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

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

ドラゴンボール風にたとえると、price1 と price2 は、同じ補給品価格パネルへ向かう2つの案内札です。

案内札が2つあっても、パネルは1つです。

パネルの中身を書き換えれば、どちらの案内札から見に行っても、同じ変更後の記録を見ることになります。

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