Java入門|値渡しと参照渡しの違いを理解する

悟空本人を渡すのか、戦闘力のメモを渡すのか 参照渡しと値渡しの違いを見抜こう

クラス型の変数を学んでくると、「オブジェクトそのものが渡されているのか、それとも別のものが渡されているのか」で迷いやすくなります。特に、メソッドの引数に基本型の変数とクラス型の変数を使ったときは、見た目は似ていても中で起きていることが違います。ここをあいまいなままにすると、なぜ変更が呼び出し元に影響するのか、なぜ影響しないのかがわかりにくくなってしまいます。

ドラゴンボールで考えると、この違いはかなりイメージしやすいです。戦闘力 9000 という数値を仲間に伝えるときは、その数字を書いたメモを渡すような感覚です。一方で、悟空という戦士そのものを紹介するときは、悟空本人につながる情報を渡す感覚になります。前者は「値のコピー」で、後者は「同じオブジェクトへの参照」です。ここでは、この2つの違いをドラゴンボールのたとえでやさしく整理していきます。

基本型とクラス型では、引数の渡され方が違う

Javaでは、メソッドに引数を渡すとき、基本型とクラス型で見え方が変わります。

基本型の変数を引数にしたときは、値がコピーされて渡されます。
たとえば int や double のような型です。

一方で、クラス型の変数を引数にしたときは、オブジェクトそのものが増えるわけではなく、同じオブジェクトを指すための情報が渡されます。

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

引数の種類渡されるもの呼び出し元との関係
基本型値のコピー呼び出し先は別の値として扱う
クラス型オブジェクトへの参照呼び出し先も同じオブジェクトを意味する

この違いが、あとで「変更が影響するかどうか」を決めるポイントになります。

ドラゴンボールで考える値渡し

まず基本型の値渡しから見てみましょう。

たとえば、悟空の戦闘力 9000 を誰かに伝えるとします。
このとき渡しているのは、悟空本人ではなく「9000 という数字」です。

つまり、

  • 呼び出し元では 戦闘力 = 9000
  • 呼び出し先には 9000 のコピーが渡る

という関係になります。

呼び出し先でその数字を 15000 に変えたとしても、それはあくまで渡された先の数字です。
もとの 9000 には影響しません。

ドラゴンボールで言えば、悟空本人を変えているのではなく、「戦闘力 9000 と書かれた紙」を書き換えているようなものです。

ドラゴンボールで考える参照渡し

次にクラス型です。

こちらは、悟空本人につながる情報を渡すイメージです。
つまり、呼び出し元も呼び出し先も、同じ悟空を見ています。

たとえば名前を設定するメソッドに、文字列オブジェクトを指す変数を渡したとします。
このとき、呼び出し元の変数が指しているオブジェクトと、呼び出し先の仮引数が指しているオブジェクトは同じものです。

ここで大切なのは、

  • オブジェクトが2つに増えるわけではない
  • 同じ1つのオブジェクトを、両方が見ている

ということです。

これは、前の節で学んだ「クラス型の変数同士を代入すると同じオブジェクトを指すようになる」という話とつながっています。

サンプルプログラムで両方を確認する

クラス型変数の応用とオブジェクトの受け渡し」で例示した Sample8.java プログラムを使って両方を確認します。

ファイル名:Sample8.java

class Saiyan
{
    private int power;
    private double energy;
    private String name;

    public Saiyan()
    {
        power = 0;
        energy = 0.0;
        name = "名無しの戦士";
        System.out.println("戦士を作成しました。");
    }

    public void setSaiyan(int p, double e)
    {
        power = p;
        energy = e;
        System.out.println("戦闘力を" + power + "に気の量を" + energy + "にしました。");
    }

    public void setName(String nm)
    {
        name = nm;
        System.out.println("名前を" + name + "にしました。");
    }

    public void show()
    {
        System.out.println("戦闘力は" + power + "です。");
        System.out.println("気の量は" + energy + "です。");
        System.out.println("名前は" + name + "です。");
    }
}

class Sample8
{
    public static void main(String[] args)
    {
        Saiyan goku;
        goku = new Saiyan();

        goku.show();

        int battlePower = 9000;
        double ki = 5000.0;
        String warriorName = "孫悟空";

        goku.setSaiyan(battlePower, ki);
        goku.setName(warriorName);

        goku.show();
    }
}

このプログラムでは、setSaiyan の引数には基本型が使われていて、setName の引数にはクラス型である String が使われています。
つまり、1つのサンプルの中で、値渡しと参照渡しの両方を見られるようになっています。

setSaiyan は値渡しの例

まず、次の呼び出しです。

goku.setSaiyan(battlePower, ki);

ここで渡しているのは、

  • int 型の battlePower
  • double 型の ki

です。

これらは基本型なので、メソッド側には値がコピーされて渡されます。
つまり、呼び出し先の仮引数 p と e は、呼び出し元の battlePower や ki そのものではありません。

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

呼び出し元呼び出し先
battlePower の値 9000p に 9000 がコピーされる
ki の値 5000.0e に 5000.0 がコピーされる

このため、メソッド内で p や e を変えても、呼び出し元の battlePower や ki そのものは変わりません。

setName は参照渡しの例

次に、こちらを見てみます。

goku.setName(warriorName);

ここで warriorName は String 型です。
String はクラス型なので、基本型のように「値だけがコピーされる」と考えるのではなく、「オブジェクトを指す情報が渡される」と考えます。

つまり、

  • 呼び出し元の warriorName が指す文字列オブジェクト
  • 呼び出し先の nm が指す文字列オブジェクト

は同じものです。

表にすると、こうです。

呼び出し元呼び出し先
warriorName → "孫悟空"nm → 同じ "孫悟空"

このように、クラス型を引数にすると、呼び出し元と呼び出し先は同じオブジェクトを意味することになります。
これが本文で説明されている参照渡しの感覚です。

なぜオブジェクトはコピーされないのか

ここで大切なのは、クラス型を引数にしたとき、オブジェクトそのものがコピーされて増えるわけではないことです。

たとえば warriorName に "孫悟空" という文字列オブジェクトが入っているとき、setName に渡したからといって、もう1つ別の "孫悟空" が自動で作られるわけではありません。

渡されるのは、あくまで「そのオブジェクトを指す情報」です。

ドラゴンボールで言えば、悟空本人を2人に増やしているのではなく、
「この人が悟空です」と同じ悟空を指し示しているだけです。

この感覚が大切です。

値渡しと参照渡しを図で整理する

上段では、呼び出し元の warriorName と、呼び出し先の nm が、同じ "孫悟空" オブジェクトを指しています。
これが参照渡しのイメージです。両者は別々のように見えても、意味しているオブジェクトは同じです。

下段では、battlePower や ki の値が、メソッドの仮引数 p や e にコピーされています。
こちらは値渡しなので、呼び出し元の値と呼び出し先の値は別物として扱われます。

この上下の違いを見ることで、クラス型と基本型で何が違うのかがかなり見えやすくなります。

この違いをなぜ覚える必要があるのか

この仕組みを理解しておくことはとても大切です。
なぜなら、メソッド呼び出しのあとで、

  • なぜ元の値が変わっていないのか
  • なぜ同じように影響が見えるのか

を正しく判断できるようになるからです。

もしここがあいまいだと、

  • 基本型なのに「元も変わる」と思い込む
  • クラス型なのに「別のオブジェクトができる」と思い込む

といった混乱が起こります。

ドラゴンボールで言えば、

  • 戦闘力の数字メモを渡したのか
  • 悟空本人につながる情報を渡したのか

を区別できるようになることが大切なのです。

いちばん大事な感覚

「値渡しと参照渡しの違いを理解する」で大切なのは、次の感覚です。

  • 基本型を引数にすると、値がコピーされて渡される
  • クラス型を引数にすると、同じオブジェクトを意味する参照が渡される
  • オブジェクトはコピーされて増えるわけではない
  • 基本型は呼び出し元と呼び出し先で別物
  • クラス型は呼び出し元と呼び出し先で同じオブジェクトを見ている

ドラゴンボールで言いかえるなら、

  • 戦闘力 9000 を渡すのは、数字のメモを渡すこと
  • 孫悟空という文字列オブジェクトを渡すのは、同じ対象を指すこと

ということです。

この感覚がつかめると、Javaのメソッド呼び出しで起こる変化をかなり正確に読み取れるようになります。クラス型変数と基本型変数の違いを本当に理解するうえで、ここはとても重要な土台です。