値渡しの罠
このレッスンでは、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は元の配列を指したまま
つまり bump と replace は、どちらも呼び出し元に何の影響も与えません。
それでも grow だけは効く
一方で grow(arr) は呼び出し元に影響します。矛盾しているように見えますが、していることが違います。
a が持っているのは「同じ配列の居場所」です。a[0] += 1 はその居場所にある配列そのものを書き換えているので、同じ配列を指している arr からも変化が見えます。変数を差し替えたのではなく、指された先の中身を触ったからです。
次のように整理してください。
| メソッド内の操作 | 呼び出し元への影響 |
|---|---|
| 引数の変数に別の値を代入する | 無い |
| 引数が指すオブジェクトの中身を変える | 有る |
不変オブジェクトだと後者も起きない
String や Integer のような不変オブジェクトは、中身を変える手段がありません。そのためメソッドに渡しても呼び出し元は絶対に変わりません。
static void edit(String s) { s = s + "!"; }
String msg = "hi";
edit(msg);
System.out.println(msg); // hi のままs + "!" は新しい String を作って s に代入しているだけです。試験では「参照型だから変わるはず」と思わせる形で出題されるので、書き換えているのが変数なのか中身なのかを毎回見分けてください。
演習で確かめます
3種類のメソッドを呼んだあとの値を返して、どれが効いてどれが効かないかを実行して確かめます。
要件
- int の変数 n と、要素1つの int 配列 arr を start から作ること
- bump, grow, replace をこの順で呼ぶこと
- n と arr[0] をカンマ区切りでつないだ文字列を返すこと
- bump, grow, replace の中身は書き換えないこと
入出力例
demo(5) → "5,6"
demo(10) → "10,11"
demo(0) → "0,1"ヒント
編集 LuaGate編集部