1. はじめに
プログラミング言語には、関数(またはメソッド)にパラメータを渡す主な方法が2つあります:
- 値渡し: 関数は変数の値のコピーを受け取ります。関数がパラメータを変更しても、元の値は変わりません。
- 参照渡し: 関数は元のオブジェクトへの「参照」(ポインタ)を受け取ります。関数内での変更は元のオブジェクトに反映されます。
Java ではパラメータはすべて値渡しです!
ただし注意点があります。プリミティブ型(int、double、boolean など)の場合は値そのものがコピーされます。参照型(たとえば配列やオブジェクト)の場合は、参照の値(つまりメモリ上のオブジェクトの「アドレス」)がコピーされ、オブジェクト自体はコピーされません。
もし頭が混乱してきても心配しないでください。これから順番に整理し、例で分かりやすく説明します。
2. プリミティブ型: 値がコピーされる
まずは簡単な例です。int 型の変数の値をメソッド内で変更しようとするとどうなるでしょうか?
public class Demo
{
public static void main(String[] args)
{
int number = 5;
changeValue(number);
System.out.println(number); // 何が出力される?
}
public static void changeValue(int n)
{
n = 42;
}
}
結果: 画面には 5 が出力されます。
なぜ?
メソッド changeValue には、変数 number の値のコピーが渡されます。メソッド内で変更しているのはコピー(n)だけで、main にある元の変数 number はそのままです。
たとえ話
友だちに渡したのがパスポートのコピー(複製)であって原本ではないと想像してください。友だちがコピーにどれだけ落書きしても、あなたの本物のパスポートは無傷のままです。
3. 参照型: 参照がコピーされる
次に、同じことを配列で試してみます。
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
changeFirst(numbers);
System.out.println(numbers[0]); // 何が出力される?
}
public static void changeFirst(int[] arr)
{
arr[0] = 99;
}
}
結果: 画面には 99 が出力されます。
なぜ?
メソッド changeFirst には、配列 numbers への参照のコピーが渡されます。main の numbers とメソッド内の arr は、どちらもメモリ上の同じ配列を指します。したがって、メソッド内で配列要素を変更すると、その変更は呼び出し側からも「見え」ます。
イメージ図
main: numbers ──► [1, 2, 3]
▲
│
changeFirst: arr ──┘
arr[0] を変更すると、実際には numbers[0] を変更していることになります。どちらも同じオブジェクトを指しているからです。
4. しかし参照そのものを変更しても元のオブジェクトは変わらない!
今度は次のようにしてみましょう。
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
replaceArray(numbers);
System.out.println(numbers[0]); // 何が出力される?
}
public static void replaceArray(int[] arr)
{
arr = new int[] {10, 20, 30};
}
}
結果: 1 が出力されます。
なぜ?
メソッド replaceArray の中で、変数 arr は新しい配列を指すようになりますが、これは main の変数 numbers には影響しません。
もともと同じ参照を共有していましたが、メソッド内で arr = ... と代入しても、それはローカルな変更にとどまります。
たとえ話
あなたと友だちが同じ家の鍵を持っている状況を想像してください。友だちが新しい鍵を作っても、あなたの古い鍵はそのままで、ドアも変わりません。
5. 確認のための例: メソッドでは2つの int を『入れ替え』できない
多くの初心者は2つの数値を入れ替えるメソッドを書こうとしますが、うまくいきません。
public class Demo
{
public static void main(String[] args)
{
int a = 5, b = 10;
swap(a, b);
System.out.println(a + " " + b); // 期待: 10 5、実際は?
}
public static void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
}
結果: 画面には 5 10 が出力されます。
なぜ?
swap に渡されるのは、a と b のコピーだからです。swap 内で行ったことはコピーに対してのみ効き、元の変数には影響しません。
6. ただし配列なら中身を変えられる!
配列の中身を入れ替える正しい方法を見てみましょう。
public class Demo
{
public static void main(String[] args)
{
int[] arr = {5, 10};
swap(arr);
System.out.println(arr[0] + " " + arr[1]); // 期待: 10 5
}
public static void swap(int[] arr)
{
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}
結果: 10 5 が出力されます。
なぜ?
元の変数とメソッドのパラメータが指している同じオブジェクト(配列)の中身を変更しているからです。
7. デモ: オブジェクトとそのフィールド
配列は理解できました。自作クラスのオブジェクトではどうでしょうか? まったく同じです。
class Box
{
int value;
}
public class Demo
{
public static void main(String[] args)
{
Box box = new Box();
box.value = 7;
changeBox(box);
System.out.println(box.value); // 何が出力される?
}
public static void changeBox(Box b)
{
b.value = 42;
}
}
結果: 42 が出力されます。
なぜ?
参照が指しているオブジェクトのフィールドを変更しているからです。
メソッドにパラメータを渡すと何が起こるか
| 何を渡すか | 何がコピーされるか | メソッド内で変更できる? | 呼び出し側から変更が見える? |
|---|---|---|---|
| int、double など | 値のコピー | はい、ただしコピーだけ | いいえ |
| 配列 | 参照のコピー | はい(要素を変更すれば) | はい |
| オブジェクト(クラス) | 参照のコピー | はい(フィールドを変更すれば) | はい |
| 配列/オブジェクト | 参照のコピー | いいえ(新しい参照を代入しても) | いいえ |
8. よくある誤り
エラー No. 1: メソッド呼び出し後に int が変わると期待する。 多くの初心者は、int 型の変数をメソッドに渡せば、その値をメソッド内で変更できると思いがちです。これは誤りです。変わるのはコピーだけです。
エラー No. 2: メソッドで2つの int を入れ替えようとする。 既に見たとおり、これは不可能です。入れ替わるのは変数のコピーだけで、元の値は影響を受けません。
エラー No. 3: メソッド経由で配列を「置き換え」ようとする。 メソッド内でパラメータの配列に新しい配列(arr = new int[]{...})を代入しても、元の配列には影響しません。配列を「置き換え」たい場合は、新しい配列をメソッドの戻り値として返し、呼び出し側の変数に代入する必要があります。
エラー No. 4: オブジェクトの中身を変更できることを忘れる。 オブジェクト(や配列)を渡す場合、そのフィールド/要素の変更は呼び出し側からも見えることを忘れないでください。
エラー No. 5: null の取り扱いの混乱。 null の参照をメソッドに渡し、そのフィールドにアクセスしようとすると、NullPointerException が発生します。注意してください!
GO TO FULL VERSION