Java の参照型は「参照の値渡し」(パート2:処理イメージ編)

0
832
views

外部メソッドへ引数を渡す方法は色々ありますが、Java の参照型変数をメソッドの引数に渡す方法では「参照の値渡し」という方法が使われます。ここでは、ひと言では分かりにくい「参照の値渡し」の意味を、メモリ内部の処理イメージしながら考えていきます。

この記事で使われるメモリ内部の処理イメージは説明しやすいように簡略化しており、あくまでも Java プログラムの挙動を理解するための手段として利用しています。使用されている図が正確なイメージではないことはご了承ください。

コード例

Java の参照型変数をメソッドの引数として渡すケースのコード例は、下記の記事で3タイプの事例を用いて行っています。

Java の参照型は「参照の値渡し」(パート1:コード編)

内部処理のイメージ

では実際にメモリでどんなことが行われているか、処理を見てみます。上記記事のコード例にイメージは対応しているので、コードを追いながら参考にしてください。

間違った「参照の値渡し」のイメージ

まずは間違った「参照の値渡し」のイメージを紹介します。

doubt-image.png

間違っているポイントは、foo と bar が同じ参照先を保持していることです。この状態は、参照の値渡しではなく、参照そのものを渡す「参照渡し」です。

メモリのスタック領域では、オブジェクトは参照先を示す値(アドレス)を保持しています。この例では1440番です。

実際のオブジェクトはヒープ領域に格納されており、例えばアドレスの1440番の位置に格納されたオブジェクトの num の値は 128 になっています。

そこでもし、メソッド内で仮引数 bar に新たなオブジェクトを代入したらどうなるでしょうか。

当然、スタック領域に保持されているオブジェクト参照情報は、新たなオブジェクト参照アドレスに置き換わります。この場合は、3551番になっています。

さらに、この新たなオブジェクトのフィールド num の値を 256 にしたら、結果的に foo.num の値は 256 になるはずです。

ところが実際にこのようなプログラムを Java で書くと、このようにはなりません。 foo.num の値は 128 になります。

なぜなら Java の参照型は、参照そのものを渡す「参照渡し」ではなく、参照の値を渡す「参照の値渡し」だからです。

仮引数のオブジェクトに新オブジェクトを代入した場合

では実際に「参照の値渡し」で、メソッド先でオブジェクトに new を代入した場合のイメージを見てみます。

new-into-formal-arg.png

参照渡しとは違い、メソッドの引数に参照の値(アドレス)を渡しています。参照の値渡しです。

メソッド内で仮引数 bar に新しいオブジェクトを代入すると、スタック領域の値は、新しいオブジェクトが格納されているヒープ領域のアドレスに置き換わります。図の例では 3511番です。

メソッド内でこのオブジェクトの num の値を 256 にしようが、foo.num の値は変わりません。bar に新しいオブジェクトを代入した時点で、foo と bar、実引数と仮引数の参照先は異なってしまったからです。

この挙動は、実際の Java プログラムの挙動に合致します。

仮引数のオブジェクトに null を代入した場合

ダメ押しの感はありますが、例をもう1つ。「参照の値渡し」で、メソッド先でオブジェクトに new を代入した場合のイメージです。

null-into-formal-arg.png

参照の値渡しのため、仮引数 bar に null を代入した時点で bar の参照先情報は無くなります。ただし、参照渡しとは違い、foo の参照先は置き変わりません。

bar の参照先は無い(null)ので、bar.num にアクセスしようとすると NullPointerException が出ます。

まとめ

最後に参照の値渡しの典型的なパターンを見て、メモリ内部の処理イメージをおさらいします。

call-by-value-of-reference.png

実引数 foo オブジェクトをメソッドに渡すと、その参照の値、図の例では 1440番が仮引数 bar に渡されます。これがまさに「参照の値渡し」です。

そしてメソッド内で、bar.num に 256 を代入するということは、ヒープ領域の1440番にアクセスし、オブジェクトの num の値を 256 に書き換えることになります。

すると、実引数と仮引数のフィールドの値が同じになり、あたかも参照渡しをしているような挙動に見えてしまいます。

しかし、new や null を代入した例からも分かるように、内部的な処理は「参照の値渡し」で行われています。