オブジェクトは参照変数である

今回はちょっと初級(?)向けのお話。Java にはプリミティブ変数と参照変数の二種類がある。int や double などはプリミティブ変数であり、宣言と同時にそれぞれ別個に領域が確保される。

class Test {
    public static void main(String[] args) {
        int a = 2;
        int b = a;
        a = 3;
        System.out.println(b);
    }
}

この例では int 型変数 a と b が別個に領域が確保され、b に a を代入するときは、そこに格納された値が代入される。したがって b には 2 が格納されている。その後 a には 3 を代入し直しているが、b の領域は別個だから、a が変更されても b に影響は及ぼさない。よってこのプログラムの実行結果は

2

である。
一方、クラスのオブジェクトは参照変数である。実体とは別に、その実体があるメモリ上のアドレスが変数に格納される。

class Test2 {
    public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer("abc");
        StringBuffer sb2 = sb1;
        sb1.append("def");
        System.out.println(sb2);
    }
}

sb2 に sb1 を代入しているが、これはアドレスの代入なので、この時点で sb1 と sb2 は同じ実体を参照するということに注意してほしい。そうすると sb1 の方からメソッドを呼び出して実体に手を加えれば、sb2 から参照したときもその変更が加えられたものが見えるはずである。事実、このプログラムの実行結果は

abcdef

となってしまう。ではこれを次のように書き換えたらどうなるか。

class Test3 {
    public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer("abc");
        StringBuffer sb2 = new StringBuffer(sb1);
        sb1.append("def");
        System.out.println(sb2);
    }
}

さっきとの違いは、sb2 に sb1 を直接代入するのではなく、コンストラクタの引数として渡している点である。こうすると、sb1 の実体に基づいた新たな実体が作られ、sb2 にはその新しい実体への参照アドレスが格納される。つまり sb1 と sb2 はオブジェクトとしては異なる。このため、今度は sb1 からメソッドを呼び出して変更しても sb2 には影響しない。つまりこのプログラムの実行結果はこうなる。

abc

オブジェクトのコピーを作りたいときはこの点に注意しないと思わぬバグを引き起こすので注意。