値渡しの罠

このレッスンでは、Java の引数の渡し方が常に値渡しであることを、参照型を含めて説明できるようになります。

まず予測してください

static void bump(int x) { x += 10; } static void grow(int[] a) { a[0] += 1; } static void replace(int[] a) { a = new int[] { 99 }; } int n = 5; int[] arr = { 5 }; bump(n); grow(arr); replace(arr); System.out.println(n + "," + arr[0]);

出力は何になるでしょうか。答えは 5,6 です。

Java の引数は例外なく値渡し

メソッドを呼ぶとき、Java は引数の値をコピーして渡します。ここに例外はありません。基本データ型でも参照型でも同じです。

  • bump(n)n の値 5 がコピーされ、メソッドの中の x が 15 になる。呼び出し元の n は 5 のまま
  • replace(arr)arr が指している場所を表す値がコピーされる。メソッドの中で a に別の配列を代入しても、コピーが書き換わるだけで、呼び出し元の arr は元の配列を指したまま

つまり bumpreplace は、どちらも呼び出し元に何の影響も与えません

それでも grow だけは効く

一方で grow(arr) は呼び出し元に影響します。矛盾しているように見えますが、していることが違います。

a が持っているのは「同じ配列の居場所」です。a[0] += 1その居場所にある配列そのものを書き換えているので、同じ配列を指している arr からも変化が見えます。変数を差し替えたのではなく、指された先の中身を触ったからです。

次のように整理してください。

メソッド内の操作呼び出し元への影響
引数の変数に別の値を代入する無い
引数が指すオブジェクトの中身を変える有る

不変オブジェクトだと後者も起きない

StringInteger のような不変オブジェクトは、中身を変える手段がありません。そのためメソッドに渡しても呼び出し元は絶対に変わりません。

static void edit(String s) { s = s + "!"; } String msg = "hi"; edit(msg); System.out.println(msg); // hi のまま

s + "!" は新しい String を作って s に代入しているだけです。試験では「参照型だから変わるはず」と思わせる形で出題されるので、書き換えているのが変数なのか中身なのかを毎回見分けてください。

演習で確かめます

3種類のメソッドを呼んだあとの値を返して、どれが効いてどれが効かないかを実行して確かめます。

要件

  1. int の変数 n と、要素1つの int 配列 arr を start から作ること
  2. bump, grow, replace をこの順で呼ぶこと
  3. n と arr[0] をカンマ区切りでつないだ文字列を返すこと
  4. bump, grow, replace の中身は書き換えないこと

入出力例

demo(5)"5,6" demo(10)"10,11" demo(0)"0,1"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.java
学習モード

メモ

値渡しの罠

⌘S で保存