Javaのパラメータ渡し
Javaではパラメータは参照渡しされているという誤解があります。というより オレがそう思っていました。゜(゚´Д`゚)゜。あるプログラムを書いてる時に、どうもおかしいと 思って悩んだ挙句、あるWebサイトで衝撃の事実を目撃したのです。そう、Javaは 参照ではなく値でパラメータ渡しを実現しています。

ところが事はそう単純ではありませんでした。何故ならオレも含めて多くの人が、 メソッドなどを通してオブジェクトを操作しているからです。間違いなく参照は 渡されているはずなのです。・・・矛盾してそうですが、矛盾していません。 例を示します。

			
/*テスト用のメインクラス*/
public class ParameterTest {
    public static void main(String[] args) {
        int intValue = 10;
        MyValue myValue = new MyValue(10);

        changeValue(intValue); //intValueを変えてみる
        changeValue(myValue);  //myValueを変えてみる
		
        /*メインでも変わってるかな?*/
        System.out.println("メイン内のvalue: " + intValue);
        System.out.println("メイン内のmyValue: " + myValue.value);
	}
	
    public static void changeValue(int value) {
        value += 10;
        System.out.println("メソッド内のvalue: " + value);
    }
	
    public static void changeValue(MyValue myValue) {
        myValue.value += 10;
        System.out.println("メソッド内のmyValue: " + myValue.value);
    }
}

/*簡単なオリジナルクラス*/
class MyValue {
    public int value;
	
    MyValue(int value) {
        this.value = value;
    }
}

このプログラムでは、mainメソッド内でint型のintValueとMyValue型のmyValueを宣言しています。 MyValueはpublicなint型の変数のみを持つシンプルなクラスです。intValueとmyValueを それぞれ10という値で初期化して、それぞれをメソッドの引数に渡し、そのメソッドで それぞれの値を変更します。その後メインの2つの変数はどんな動作をするでしょうか?

実行結果は下のようになります。


プリミティブ値であるint型のintValueはmainメソッド内では値は変わっていません。 これはどんな入門書にも書いてあることですし、簡単なことです。 つまりプリミティブ値は、値そのものをコピーして渡します。 changeValue内で変更しているのはコピーされた変数なので、コピーもとの mainメソッドでは何も変わりません。

それに対してmyValueはどうでしょう。changeValue内で施した変更が、mainメソッドでも 反映されています。これは引数にコピーした値を渡していたとしたらあり得ない事です。 それでもJavaでは、パラメータは値渡しされています。どういうことでしょうか?

簡単に言うと、Javaでは「参照」を値渡ししています。参照というのはオブジェクトのある場所ですから、 メモリのアドレスと考えられます。つまり、このメモリのアドレスをコピーして(値渡しで)メソッドに渡しています。 先ほどの例だと、changeValue(MyValue)に渡されたのは、myValueのアドレスそのものでも、 myValueそのもののコピー(これは浅いコピーも深いコピーも含む。cloneを参照。)でもなく、 myValueのアドレスのコピーなのです。そのコピーされたアドレスを、値渡ししているのです。

ここで1つの疑問が湧きます。「オブジェクトのアドレスそのもの」を渡す事と 「オブジェクトのアドレスのコピー」を渡す事にはどんな違いがあるのでしょうか? この違いこそが、(オレも含めて)Javaのパラメータ渡しについて誤解していることによって 引き起こされる問題の理由となるものです

その違いを説明するために、もう1つの例を示します。以下のコードを見てください。

			
/*テスト用のメインクラス*/
public class ParameterTest2 {
    public static void main(String[] args) {
        MyValue myValue = new MyValue(10);

        System.out.println("メイン1: " + myValue.value);
		
        changeInstance(myValue);  //インスタンスを変更してみる
		
        /*どうなるかな?*/
        System.out.println("メイン2: " + myValue.value);
    }
	
    public static void changeInstance(MyValue myValue) {
        myValue = new MyValue(20);
        System.out.println("メソッド: " + myValue.value);
    }
}

MyValueは先程と同じなので省略します。次に実行結果を見てください。


今度はどうでしょうか?同じようにメソッドに引数を渡しているのに、mainの値は変わっていません。 つまり呼び出したメソッドでの変更が呼び出し元には反映されていないことになります。 これは正に値渡しのように動作していますよね。

次にメソッドでの処理を見てみると、myValueの参照を変更していることが分かります。 メソッド内では確かにmyValue.valueは20になっています。

この違いが、先程書いた「オブジェクトのアドレスそのもの」を渡す事と 「オブジェクトのアドレスのコピー」を渡す事の違いです。両方とも、 参照の先にあるものを変更することが出来ますが、後者はの参照そのものを 変更することは出来ません。したがって上記のような実行結果になります。Javaの パラメータ渡しで値渡しされるのは参照です。参照を知ることが出来れば、その参照が指し示す オブジェクトにアクセス出来ます。ですが、参照を知っていても参照そのものを変更出来るとは限りません。 なぜなら、参照がコピーされた値かも知れないからで、Javaでは正にそのように動作するからです。

ここで扱ったことは誤解しやすいことですが、一度分かってしまえば簡単なことです。 大切なのは、「どうすればいいか」を考えるだけでなく「何故そうなるか」ということも 深く考察し実験することだと思います。