Java超|値渡しと参照渡しの違い

数値を渡すのか、戦士を指す登録札を渡すのか。値渡しと参照の受け渡しを理解すると、メソッド呼び出し後に「どこが変わるのか」がはっきり見えるようになります。

クラス型変数を学んでくると、メソッドに引数を渡したときに、何が渡されているのかが気になってきます。

たとえば、int 型の数値を渡す場合と、String 型の文字列を渡す場合では、どちらも見た目はメソッドに引数を渡しているだけに見えます。

しかし、中で起きていることの見え方は少し違います。

ドラゴンボールの世界観でたとえると、基本型の値を渡すのは、戦闘力 9000 と書かれた紙を渡すようなものです。

紙に書かれた数字を相手に渡しているだけなので、相手側でその紙の数字を書き換えても、本部にある元の記録まで自動で変わるわけではありません。

一方、クラス型の変数を渡す場合は、戦士本人を丸ごと複製して渡すのではありません。
その戦士や文字列オブジェクトを指し示す登録札を渡すようなものです。

呼び出し元と呼び出し先が、同じ対象を見られるようになります。

ただし、Javaについて正確に言うと、Javaは常に値渡しです。

基本型では、数値そのもののコピーが渡されます。
クラス型では、オブジェクトへの参照という値がコピーされて渡されます。

つまり、クラス型の場合も、参照そのものがコピーされて渡ると考えるのが大切です。

学習上は、分かりやすく 参照を渡す と表現することがあります。
ただし、正確には 参照の値がコピーされて渡される という感覚で整理すると、Javaのしくみをかなり正しく理解できます。

この記事では、指定された Sample8.java を使いながら、基本型の値渡しと、クラス型における参照の値渡しの違いを、ドラゴンボール風の戦士管理イメージで解説していきます。

基本型とクラス型では、渡されるものの見え方が違う

Javaでメソッドに引数を渡すとき、基本型とクラス型では、受け取る側から見えるものが違います。

基本型の場合は、値そのものがコピーされます。

たとえば、int 型の battlePower に 9000 が入っているとします。
この battlePower をメソッドに渡すと、メソッド側の仮引数には 9000 という値がコピーされます。

一方で、クラス型の場合は、オブジェクトそのものが複製されるわけではありません。
オブジェクトを指している参照の値がコピーされます。

引数の種類渡されるもの呼び出し元との関係ドラゴンボール風のたとえ
基本型値のコピー呼び出し先は別の値として扱う戦闘力を書いた紙を渡す
クラス型参照のコピー呼び出し先も同じ対象を指せる戦士や名前札を指す登録札を渡す

ここで大事なのは、どちらもコピーは発生しているという点です。

基本型では、数値そのものがコピーされます。
クラス型では、参照という値がコピーされます。

オブジェクト本体がコピーされるわけではありません。

この違いが、メソッドを呼び出したあとに、呼び出し元へ影響するのか、しないのかを考える土台になります。

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

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

たとえば、サイヤ人戦士の戦闘力 9000 を、修行管理係に伝えるとします。

このとき渡しているのは、戦士本人ではありません。
戦闘力 9000 という数値です。

ドラゴンボール風にたとえると、次のような流れです。

場面イメージ
呼び出し元戦闘力 9000 を持っている
引数として渡す9000 と書かれた紙を渡す
呼び出し先9000 のコピーを受け取る
呼び出し先で変更コピー側を書き換えるだけ

呼び出し先で、その値を 12000 に変更したとしても、それは呼び出し先で受け取ったコピーを変えただけです。

呼び出し元の battlePower そのものが自動で変わるわけではありません。

つまり、基本型では、渡した先で仮引数を変更しても、呼び出し元の変数は別物と考えます。

ドラゴンボール風に言うと、本部にある戦闘力台帳と、係に渡した写しの紙は別です。
写しの紙に赤ペンで 12000 と書いても、本部の台帳が自動で書き換わるわけではありません。

ドラゴンボール風に考える参照の受け渡し

次に、クラス型を見ていきましょう。

クラス型の変数は、オブジェクトそのものではなく、オブジェクトを指す参照を持っています。

たとえば、次のような変数があるとします。

String warriorName = "孫悟空";

warriorName は、文字列オブジェクトを指しています。

この warriorName をメソッドに渡すと、文字列オブジェクトそのものがもう1つ作られるわけではありません。
warriorName が持っている参照の値が、メソッド側の仮引数にコピーされます。

ドラゴンボール風にたとえると、こうです。

場面イメージ
呼び出し元孫悟空 と書かれた名前札を指している
引数として渡すその名前札を指す情報を渡す
呼び出し先同じ名前札を指せるようになる
オブジェクト本体複製されて増えるわけではない

つまり、呼び出し元と呼び出し先が、同じ文字列オブジェクトを見られる状態になります。

この感覚を、学習上は 参照を渡す と表現することがあります。
ただし、Javaの正確なしくみとしては、参照の値がコピーされて渡されています。

図:値渡しと参照の受け渡しの違い

この図が示していること

この図では、基本型を渡す場合と、クラス型を渡す場合の違いを表しています。

上段では、battlePower の 9000 と kiPower の 5000.0 が、仮引数 b と k にコピーされています。
これは基本型の値渡しです。

呼び出し元の変数と、呼び出し先の仮引数は別の変数として扱われます。

下段では、warriorName が指している String オブジェクトへの参照の値が、仮引数 nm にコピーされています。
そのため、warriorName と nm は、同じ 孫悟空 の String オブジェクトを指しています。

ここから分かるのは、クラス型を渡してもオブジェクト本体がコピーされるわけではないということです。
コピーされるのは、オブジェクトを指す参照の値です。

基本型とクラス型の引数をSample8.javaで確認する

ここでは、前の記事で使用した Sample8.java を使って、基本型とクラス型の引数を確認します。

ファイル名:Sample8.java

class SaiyanWarrior
{
    private int battlePower;
    private double kiPower;
    private String name;

    public SaiyanWarrior()
    {
        battlePower = 0;
        kiPower = 0.0;
        name = "名無しの戦士";
        System.out.println("戦士を生成しました。");
    }

    public void setWarrior(int b, double k)
    {
        battlePower = b;
        kiPower = k;
        System.out.println("戦闘力を" + battlePower + "、気の力を" + kiPower + "に設定しました。");
    }

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

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

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

        goku.show();

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

        goku.setWarrior(battlePower, kiPower);
        goku.setName(warriorName);

        goku.show();
    }
}

このプログラムでは、setWarrior() に基本型の値を渡しています。

goku.setWarrior(battlePower, kiPower);

battlePower は int 型です。
kiPower は double 型です。

つまり、ここでは基本型の値がコピーされて渡されています。

一方で、setName() には String 型の変数を渡しています。

goku.setName(warriorName);

String はクラス型です。
そのため、文字列オブジェクトへの参照の値がコピーされて、仮引数 nm に渡されます。

この1つのサンプルの中で、基本型の値渡しと、クラス型の参照の値渡しを確認できます。

setWarrior() は基本型を渡す例

まず、setWarrior() を見てみましょう。

public void setWarrior(int b, double k)
{
    battlePower = b;
    kiPower = k;
    System.out.println("戦闘力を" + battlePower + "、気の力を" + kiPower + "に設定しました。");
}

呼び出し側では、次のように書いています。

int battlePower = 9000;
double kiPower = 5000.0;

goku.setWarrior(battlePower, kiPower);

このとき、呼び出し元の battlePower と kiPower の値が、メソッド側の b と k にコピーされます。

呼び出し元呼び出し先渡されるもの
battlePowerb9000 のコピー
kiPowerk5000.0 のコピー

ここで b と k は、呼び出し元の変数そのものではありません。
呼び出し元の値をコピーして受け取った、メソッド側の別の変数です。

ドラゴンボール風にたとえると、battlePower = 9000 と kiPower = 5000.0 は、本部にある元の戦士データです。
setWarrior() には、その数値を書き写した紙が渡されます。

紙に書かれた数値をもとに、SaiyanWarrior オブジェクトの battlePower フィールドと kiPower フィールドを設定しているわけです。

基本型の仮引数を変えても呼び出し元の変数は変わらない

基本型では、値のコピーが渡されます。

そのため、仮にメソッドの中で b や k の値を変更しても、呼び出し元の battlePower や kiPower が自動で変わるわけではありません。

変数どこにあるか関係
battlePowermain メソッド内呼び出し元の変数
bsetWarrior() 内コピーを受け取った仮引数
kiPowermain メソッド内呼び出し元の変数
ksetWarrior() 内コピーを受け取った仮引数

このように、基本型では呼び出し元と呼び出し先で、変数が分かれます。

ドラゴンボール風にたとえると、元の戦士データ台帳と、メソッド側に渡された写しの紙は別物です。
写しの紙にメモを加えても、元の台帳が自動で書き換わるわけではありません。

setName() はクラス型を渡す例

次に、setName() を見てみましょう。

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

呼び出し側では、次のように書いています。

String warriorName = "孫悟空";

goku.setName(warriorName);

warriorName は String 型です。
String はクラス型なので、warriorName には文字列オブジェクトへの参照が入っています。

setName(warriorName) を呼び出すと、その参照の値が仮引数 nm にコピーされます。

呼び出し元呼び出し先渡されるもの
warriorNamenm孫悟空 を指す参照のコピー

このとき、文字列オブジェクトがもう1つ自動で作られるわけではありません。

呼び出し元の warriorName と、呼び出し先の nm は、同じ文字列オブジェクトを指す参照を持つことになります。

ドラゴンボール風にたとえると、孫悟空 と書かれた名前札が1つあります。
warriorName も nm も、その同じ名前札を指している状態です。

name = nm で何が起きているのか

setName() の中では、次の処理が行われています。

name = nm;

ここで、SaiyanWarrior オブジェクトのフィールド name に、仮引数 nm が持っている参照を代入しています。

つまり、name も同じ文字列オブジェクトを指すようになります。

変数、フィールド指しているもの
warriorName孫悟空 の String オブジェクト
nm同じ 孫悟空 の String オブジェクト
name同じ 孫悟空 の String オブジェクト

ドラゴンボール風にたとえると、呼び出し元の名前札、メソッド側の受け取り札、戦士オブジェクトの名前欄が、同じ名前情報を指すようになるイメージです。

ここでも、文字列オブジェクトが何個も増えるわけではありません。
参照の値が受け渡され、共有されていると考えます。

図:呼び出し元に影響する場合としない場合

この図が示していること

この図では、基本型を渡した場合と、クラス型を渡した場合の違いを比較しています。

左側では、battlePower の 9000 が仮引数 b にコピーされています。
メソッド側で b を 12000 に変えても、呼び出し元の battlePower は 9000 のままです。

これは、基本型では値のコピーが渡されるからです。

右側では、warriorName が持つ参照の値が nm にコピーされています。
そのため、warriorName と nm は同じ String オブジェクトを指しています。

ただし、オブジェクト本体が増えたわけではありません。

ここから分かるのは、基本型では値が別々になり、クラス型では参照先を共有できるということです。
Javaでは、クラス型でも参照の値がコピーされて渡される、という点も大切です。

Javaでは厳密には参照の値渡し

学習では、基本型は値渡し、クラス型は参照渡しという言い方をすることがあります。

ただし、Javaの厳密な説明では、すべて値渡しです。

では、クラス型の場合は何の値が渡されるのでしょうか。

それは、参照の値です。

Javaで渡される値
int整数値のコピー
double小数値のコピー
StringString オブジェクトへの参照のコピー
SaiyanWarriorSaiyanWarrior オブジェクトへの参照のコピー

つまり、基本型もクラス型も、メソッドへ渡されるときには値がコピーされます。

違うのは、その値が 数値そのもの なのか、オブジェクトを指す参照 なのかです。

ドラゴンボール風にたとえると、基本型では数字の紙そのものをコピーして渡します。
クラス型では、戦士や名前札へつながる登録札の情報をコピーして渡します。

この見方ができると、Javaの引数のしくみをかなり正確に理解できます。

参照を渡すと呼び出し元にも必ず影響するのか

ここは少し丁寧に区別しておきたいところです。

クラス型の引数では、同じオブジェクトを指す参照の値が渡されます。
そのため、メソッドの中でそのオブジェクトの中身を変更すると、呼び出し元から見てもその変更が見えることがあります。

ただし、仮引数そのものに別のオブジェクトを代入しても、呼び出し元の変数が自動で別の参照先に変わるわけではありません。

つまり、次の2つは分けて考えます。

メソッド内で行うこと呼び出し元への影響
参照先オブジェクトの状態を変更する呼び出し元からも変化が見えることがある
仮引数に別の参照を代入する呼び出し元の変数そのものは変わらない

これは、Javaが 参照の値渡し だからです。

ドラゴンボール風にたとえると、同じ戦士記録を見ている状態で、その記録内容を書き換えれば、他の札から見ても変更が分かります。

しかし、受け取った札 nm を別の名前札に差し替えても、呼び出し元の warriorName の札まで勝手に差し替わるわけではありません。

この区別を押さえると、Javaは参照渡しなのか、値渡しなのかで混乱しにくくなります。

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

クラス型の変数をメソッドに渡すとき、初心者のうちは、オブジェクトがまるごとコピーされるのかなと思いやすいです。

しかし、Javaではそうではありません。

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

渡されるのは、参照の値です。

思いやすい誤解実際の動き
オブジェクト本体がコピーされる本体はコピーされない
呼び出し先に別オブジェクトができる同じオブジェクトを指す参照を受け取る
渡すたびに実体が増える実体は増えない

ドラゴンボール風にたとえると、悟空本人を2人に増やしているわけではありません。
この名前札を見てください と同じ対象を指す情報を渡しているだけです。

この感覚が、クラス型変数の受け渡しではとても大切です。

Sample8.java の実行の意味

Sample8.java を実行すると、最初に戦士が生成され、初期状態が表示されます。

初期状態では、戦闘力は 0、気の力は 0.0、名前は 名無しの戦士 です。

そのあとで、次の変数を用意します。

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

この時点で、battlePower と kiPower は基本型の値を持っています。
warriorName は String オブジェクトへの参照を持っています。

そのあと、次の2つのメソッドを呼び出します。

goku.setWarrior(battlePower, kiPower);
goku.setName(warriorName);

setWarrior() では、battlePower と kiPower の値がコピーされます。
setName() では、warriorName が持つ参照の値がコピーされます。

結果として、goku オブジェクトの battlePower、kiPower、name フィールドが設定されます。

実行結果の流れは、次のようになります。

戦士を生成しました。
戦闘力は0です。
気の力は0.0です。
名前は名無しの戦士です。
戦闘力を9000、気の力を5000.0に設定しました。
名前を孫悟空にしました。
戦闘力は9000です。
気の力は5000.0です。
名前は孫悟空です。

ここで大切なのは、setWarrior() と setName() のどちらも、引数として渡された情報を使って、goku オブジェクトのフィールドを設定しているということです。

図:Sample8.javaで見る値と参照の受け渡し

この図が示していること

この図では、Sample8.java の中で、基本型の値とクラス型の参照がどのようにメソッドへ渡されるかを表しています。

battlePower と kiPower は基本型なので、setWarrior() の仮引数 b と k に値がコピーされます。

一方、warriorName は String 型なので、setName() の仮引数 nm に参照の値がコピーされます。

そのあと、name = nm によって、SaiyanWarrior オブジェクトの name フィールドも、同じ String オブジェクト 孫悟空 を指すようになります。

ここから分かるのは、Sample8.java では、基本型の値渡しと、クラス型における参照の値渡しが同時に確認できるということです。

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

値渡しと参照の受け渡しを理解することは、とても大切です。

理由は、メソッド呼び出し後に起きる変化を正しく読めるようになるからです。

たとえば、次のような混乱を防げます。

混乱しやすい考え正しい見方
基本型を渡したら元の変数も変わるはず基本型は値のコピーなので、元の変数は別
クラス型を渡したらオブジェクトが複製されるはずオブジェクト本体は複製されない
参照を渡したら呼び出し元の変数自体も自由に変わるはず仮引数の参照を入れ替えても、呼び出し元の変数は変わらない
変更が見える理由が分からない同じ参照先のオブジェクトを見ているから

ドラゴンボール風にたとえると、数字の紙を渡したのか、戦士記録につながる登録札を渡したのかを区別できるようになることです。

この違いが見えていると、コードを読んだときに、ここで変わるのはコピー側だけだな、ここでは同じオブジェクトの状態を変えているな、と判断できるようになります。

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

値渡しと参照の受け渡しでは、次の感覚が大切です。

ポイント内容
Javaは常に値渡し基本型もクラス型も、値がコピーされる
基本型では数値そのものがコピーされるint や double は値のコピー
クラス型では参照の値がコピーされるString や SaiyanWarrior は参照のコピー
オブジェクト本体は自動でコピーされない実体が増えるわけではない
参照先の状態変更は見えることがある同じオブジェクトを指しているため
仮引数の参照を入れ替えても元の変数は変わらない参照の値もコピーだから
Sample8.java では両方を確認できるsetWarrior() は基本型、setName() はクラス型

ドラゴンボール風に言えば、基本型は戦闘力を書いた紙のコピーを渡すイメージです。
クラス型は、戦士や名前札を指す登録札の情報をコピーして渡すイメージです。

どちらもコピーはされています。

ただし、コピーされるものが違います。

基本型では数値そのもの。
クラス型では参照の値。

この違いが分かると、Javaのメソッド呼び出しで何が起きているのかが、かなり見通しよく理解できるようになります。