Java道|オブジェクトはコピーされない?参照のしくみ

オブジェクトが増えたように見えても、実は増えていないことがある。
参照のしくみが分かると、Javaのクラス型変数の正体がすっきり見えてきます。

ここまでの学習で、クラスからオブジェクトを作り、それを変数で扱う流れにだいぶ慣れてきたと思います。
ただ、ここでひとつ、とても大切な疑問が出てきます。

クラス型の変数に別の変数を代入したとき、オブジェクトはコピーされているのか。
それとも、何か別のことが起きているのか。

この問いにしっかり答えられるようになると、Javaのオブジェクト指向の理解がぐっと深まります。

鬼滅の刃の世界でたとえると、隊士そのものと、その隊士を指し示す札は別物です。
たとえば炭治郎という隊士がいたとして、変数は炭治郎本人ではなく、炭治郎を指している札のようなものです。

この札を別の変数へ渡したとき、炭治郎がもう1人増えるわけではありません。
同じ炭治郎を、別の札でも指せるようになるだけです。

この感覚が、参照のしくみです。

クラス型変数は「隊士そのもの」ではない

まず最初に、ここをはっきり押さえておきましょう。

クラス型の変数は、オブジェクトそのものを入れているわけではありません。
その変数が持っているのは、どのオブジェクトを指しているかという情報です。

鬼滅の刃の世界に置き換えると、次のように考えると分かりやすいです。

概念鬼滅の刃でのたとえ
クラス隊士の設計図
オブジェクト実際に生まれた隊士
クラス型変数その隊士を指し示す札

つまり、変数は実体そのものではなく、実体への手がかりを持っているということです。

この違いはとても重要です。
ここがあいまいなままだと、あとで配列やコレクション、メソッドの引数、オブジェクトの受け渡しなどで混乱しやすくなります。

宣言しただけでは、まだ隊士は存在しない

クラス型変数を宣言しただけでは、まだオブジェクトは作られていません。

たとえば、DemonSlayer tanjiro; と宣言したときに作られているのは、炭治郎を指すための札だけです。
この段階では、その札がまだ誰を指すかは決まっていません。

実際にオブジェクトが作られるのは、new を使ったときです。

この流れを鬼滅の刃風に整理すると、次のようになります。

処理起きていること
変数を宣言する隊士を指すための札を用意する
new で生成する実際の隊士を生み出す
変数へ代入する札がその隊士を指すようになる

この流れを意識しておくと、変数とオブジェクトを別物として見られるようになります。

同じオブジェクトを複数の変数で扱う

ここからは、実際のコードで見ていきましょう。
まずは、1つのオブジェクトを2つの変数で扱う例です。

ファイル名:Sample6.java

class DemonSlayer
{
    private int strength;
    private double breathingPower;

    public DemonSlayer()
    {
        strength = 0;
        breathingPower = 0.0;
        System.out.println("隊士を生成しました。");
    }

    public void setSlayer(int s, double b)
    {
        strength = s;
        breathingPower = b;
        System.out.println("剣技の強さを" + strength + "、呼吸の力を" + breathingPower + "に設定しました。");
    }

    public void show()
    {
        System.out.println("剣技の強さは" + strength + "です。");
        System.out.println("呼吸の力は" + breathingPower + "です。");
    }
}

class Sample6
{
    public static void main(String[] args)
    {
        DemonSlayer tanjiro;
        System.out.println("tanjiroを宣言しました。");

        tanjiro = new DemonSlayer();
        tanjiro.setSlayer(9000, 5000.0);

        DemonSlayer zenitsu;
        System.out.println("zenitsuを宣言しました。");

        zenitsu = tanjiro;
        System.out.println("zenitsuにtanjiroを代入しました。");

        System.out.print("tanjiroがさす");
        tanjiro.show();

        System.out.print("zenitsuがさす");
        zenitsu.show();
    }
}

このプログラムで特に大切なのは、次の部分です。

zenitsu = tanjiro;

ここで起きていることは、隊士のコピーではありません。
tanjiro が指していた隊士を、zenitsu も指すようになっただけです。

つまり、状態はこうなっています。

変数指している先
tanjiro1人の DemonSlayer オブジェクト
zenitsu同じ DemonSlayer オブジェクト

ここで大事なのは、炭治郎と善逸という2人の隊士オブジェクトがあるわけではないということです。
変数名は2つありますが、指している先は1つです。

鬼滅の刃でたとえると、1人の隊士に対して、札が2枚ついている状態です。
札が2枚あるからといって、隊士が2人に増えたわけではありません。

Sample6.java で見えてくる参照の正体

Sample6.java の流れを順番に整理すると、次のようになります。

順番処理起きていること
1DemonSlayer tanjiro;tanjiro という札を用意する
2tanjiro = new DemonSlayer();新しい隊士を生成し、tanjiro が指す
3tanjiro.setSlayer(...);その隊士の状態を設定する
4DemonSlayer zenitsu;zenitsu という別の札を用意する
5zenitsu = tanjiro;zenitsu も同じ隊士を指す
6tanjiro.show();同じ隊士の状態を表示する
7zenitsu.show();やはり同じ隊士の状態を表示する

この流れから分かるのは、クラス型変数の代入は、オブジェクト本体を丸ごと複製する操作ではないということです。

代入で渡されるのは、オブジェクトそのものではなく、オブジェクトを指している参照です。

図:変数はオブジェクトそのものではなく参照

↓クリックすると拡大表示されます。

この図が示していること

この図では、tanjiro と zenitsu という2つの変数が、同じ1つの DemonSlayer オブジェクトを指していることを表しています。

ここから分かるのは、変数はオブジェクトそのものではなく、オブジェクトへの参照を持っているということです。
変数が2つあるからといって、オブジェクトが2つあるとは限りません。

参照先が同じなら、扱っている実体も同じです。
これが、クラス型変数の代入で最も大切なポイントです。

片方を変更すると、もう片方にも影響する

次に見たいのが、同じオブジェクトを2つの変数が指しているとき、片方から状態を変更するとどうなるかです。

ファイル名:Sample7.java

class DemonSlayer
{
    private int strength;
    private double breathingPower;

    public DemonSlayer()
    {
        strength = 0;
        breathingPower = 0.0;
        System.out.println("隊士を生成しました。");
    }

    public void setSlayer(int s, double b)
    {
        strength = s;
        breathingPower = b;
        System.out.println("剣技の強さを" + strength + "、呼吸の力を" + breathingPower + "に設定しました。");
    }

    public void show()
    {
        System.out.println("剣技の強さは" + strength + "です。");
        System.out.println("呼吸の力は" + breathingPower + "です。");
    }
}

class Sample7
{
    public static void main(String[] args)
    {
        DemonSlayer tanjiro = new DemonSlayer();
        tanjiro.setSlayer(9000, 5000.0);

        DemonSlayer zenitsu = tanjiro;

        System.out.println("炭治郎の状態を変更します。");

        tanjiro.setSlayer(12000, 8000.0);

        System.out.print("tanjiroがさす");
        tanjiro.show();

        System.out.print("zenitsuがさす");
        zenitsu.show();
    }
}

このプログラムでは、tanjiro と zenitsu が同じオブジェクトを指している状態で、tanjiro から setSlayer() を呼び出しています。

すると、表示結果では zenitsu から見た状態も変わります。

なぜかというと、両方が見ている先が同じだからです。

たとえば、変化の流れを表にするとこうなります。

タイミングtanjiro が指す状態zenitsu が指す状態
代入直後剣技の強さ 9000 / 呼吸の力 5000.0同じ
tanjiro.setSlayer(12000, 8000.0) の後剣技の強さ 12000 / 呼吸の力 8000.0同じ

ここで注目したいのは、tanjiro の中身だけが変わったのではないということです。
そもそも tanjiro と zenitsu は、それぞれ別の隊士を持っているわけではありません。

同じ1人の隊士を見ているので、その隊士が変われば、どちらから見ても結果は同じになります。

鬼滅の刃でたとえると、1人の隊士の訓練記録を2枚の札が指している状態です。
その訓練記録を書き換えたら、どちらの札から見ても新しい内容が見える、というわけです。

参照の代入は「コピー」ではない

初心者のうちは、次のように思いやすいです。

vegeta = goku のような代入があったら、goku の中身がそのまま複製されて、別の新しいオブジェクトができるのではないか。

でも、クラス型変数ではそうなりません。

代入で起きるのは、参照のコピーです。
つまり、どのオブジェクトを指しているかという情報がコピーされるだけです。

この違いを表で整理すると、次のようになります。

イメージ実際に起きていること
オブジェクト本体が複製されるそうではない
参照先の情報が渡されるこれが正しい
別々のオブジェクトになるならない
同じオブジェクトを共有するそうなる

この感覚は、Javaでとても重要です。
クラス型変数は、値型の変数とは扱いが違います。

たとえば int 型の変数なら、値そのものが代入されます。
でも、クラス型変数では、実体ではなく参照が代入されます。

図:片方から変更すると両方に影響する

↓クリックすると拡大表示されます。

この図が示していること

この図では、tanjiro と zenitsu が同じオブジェクトを指している状態で、tanjiro から状態変更を行った結果、その変化が zenitsu 側にも見えることを表しています。

ここから分かるのは、参照先が同じである限り、オブジェクトの状態は共有されるということです。
変数ごとに別々の状態を持っているわけではありません。

そのため、片方から変更した内容は、もう片方から見ても同じように確認できます。

null は「誰も指していない状態」

参照のしくみを理解するうえで、もうひとつ大切なのが null です。

null は、何も参照していない状態を表します。

鬼滅の刃でたとえると、隊士を指していた札のひもが外れて、もう誰も指していない状態です。

たとえば、ある変数にオブジェクトが入っていたとしても、その変数へ null を代入すると、その変数は参照先を失います。

このときの感覚を表にすると、次のようになります。

状態意味
変数がオブジェクトを参照しているどの隊士を指すか決まっている
変数が null である誰も指していない
null の変数からメソッドを呼ぶ参照先がないので問題が起きる

null は「空っぽのオブジェクト」ではありません。
「何も指していない」という状態そのものです。

ここはよく誤解しやすいところです。
null という特別なオブジェクトがあるわけではなく、参照先がないという意味です。

↓クリックすると拡大表示されます。

参照がなくなると、オブジェクトはどうなるか

もし、あるオブジェクトをどの変数も参照しなくなったら、そのオブジェクトには到達できなくなります。

Javaでは、このような不要になったオブジェクトを自動で片づける仕組みがあります。
これがガーベッジコレクションです。

鬼滅の刃でたとえると、どの札からも辿れなくなった記録が、本部によって整理されるようなイメージです。

表で整理すると、こうなります。

状況結果
少なくとも1つの変数が参照しているまだ使える
どの変数も参照していない不要なオブジェクトになる
不要になったオブジェクトJavaが自動的に回収対象にする

ここで大切なのは、Javaではプログラマが毎回手作業で削除しなくてもよい、という点です。
もちろん、いつ回収されるかはJavaの仕組みに任されますが、不要なオブジェクトを自動的に整理してくれるのは大きな助けです。

値型の代入との違い

参照をよりはっきり理解するために、値型との違いも見ておくと分かりやすいです。

項目int などの値型クラス型変数
代入されるもの値そのもの参照
代入後の関係別々の値として扱われる同じオブジェクトを指すことがある
一方を変更したときもう一方には影響しない同じ参照先なら影響する

たとえば int 型なら、a = b をしたあとで a を変えても、b は変わりません。
でもクラス型変数では、a と b が同じオブジェクトを指しているなら、そのオブジェクトの状態変更は両方に見えます。

ここが、参照型の大きな特徴です。

参照のしくみが分かると見え方が変わる

この参照の考え方が分かると、Javaのクラス型変数に対する見方がかなり変わります。

これまで「変数にオブジェクトを入れている」とふんわり理解していたものが、
「変数にはオブジェクトそのものではなく、オブジェクトへの参照が入っている」
とはっきり言えるようになります。

その結果、次のようなことが理解しやすくなります。

理解しやすくなること理由
配列の中にオブジェクトを入れるしくみ配列には参照が並ぶから
メソッドへオブジェクトを渡す感覚参照が渡されるから
複数の変数で同じ実体を扱う状況参照先を共有できるから
オブジェクトのコピーが難しく感じる理由単純代入では本体は複製されないから

特に、これから配列やコレクションを学ぶときには、この感覚がとても役立ちます。
オブジェクトを扱う世界では、「実体」と「参照」を分けて考えることが本当に大切です。

ここで押さえておきたい重要ポイント

最後に、このテーマで特に大切なポイントを整理しておきます。

ポイント内容
クラス型変数はオブジェクトそのものではないオブジェクトを指す参照を持つ
変数の代入で起きるのは本体のコピーではない参照先の共有が起きる
複数の変数が同じオブジェクトを指すことがあるそのとき実体は1つだけ
片方から状態を変えるともう片方にも見える参照先が同じだから
null は参照先がない状態空のオブジェクトではない
参照されなくなったオブジェクトは回収対象になるJavaが自動で管理する

鬼滅の刃でたとえるなら、変数は隊士本人ではなく、その隊士を指し示す札です。
札を別の変数へ渡しても、隊士が増えるわけではありません。
同じ隊士を別の札でも見られるようになるだけです。

この感覚をしっかり持てると、Javaのオブジェクト指向はぐっと理解しやすくなります。
クラス型変数の正体が見えてくると、この先の学習でも「なぜそうなるのか」が自然に分かる場面が増えてきます。

参照は、Javaを学ぶうえで本当に大切な土台です。
今のうちに、変数は実体ではなく参照を持っている、という感覚をしっかり身につけておくと、この先の理解がかなり楽になります。