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

数値を渡すのか、隊士を指す札を渡すのか。
値渡しと参照の受け渡しを理解すると、メソッド呼び出し後の変化が見えるようになる。

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

たとえば、int 型の数値を渡した場合と、String 型の文字列を渡した場合では、見た目はどちらも引数として渡しているだけに見えます。
しかし、中で起きていることの考え方は少し違います。

鬼滅の刃の世界でたとえると、基本型の値を渡すのは、剣技の強さ 9000 と書かれた紙を渡すようなものです。
紙に書かれた数字を相手に渡しているだけなので、相手側でその紙の数字を書き換えても、もとの記録まで変わるわけではありません。

一方、クラス型の変数を渡す場合は、隊士本人を丸ごと複製して渡すのではなく、その隊士や文字列オブジェクトを指し示す札を渡すようなものです。
呼び出し元と呼び出し先が、同じ対象を見られるようになります。

ただし、Javaについて正確に言うと、Javaは常に値渡しです。
基本型では数値そのもののコピーが渡されます。
クラス型では、オブジェクトへの参照という値がコピーされて渡されます。

つまり、クラス型の場合も「参照そのものがコピーされて渡る」と考えるのが大切です。
この記事では、学習上のイメージとして「参照を渡す」と表現しながら、実際には参照の値が渡されている、という感覚で整理していきます。

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

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

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

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

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

引数の種類渡されるもの呼び出し元との関係鬼滅の刃でたとえると
基本型値のコピー呼び出し先は別の値として扱う数字を書いた紙を渡す
クラス型参照のコピー呼び出し先も同じ対象を指せる隊士や名前札を指す札を渡す

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

基本型では、数値そのものがコピーされます。
クラス型では、参照という値がコピーされます。
オブジェクト本体がコピーされるわけではありません。

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

鬼滅の刃で考える値渡し

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

たとえば、隊士の剣技の強さ 9000 を別の係に伝えるとします。

このとき渡しているのは、隊士本人ではありません。
剣技の強さ 9000 という数値です。

鬼滅の刃でたとえると、次のような流れです。

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

呼び出し先で、その値を 12000 に変更したとしても、それは呼び出し先で受け取ったコピーを変えただけです。
呼び出し元の strength そのものが自動で変わるわけではありません。

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

これは、メソッドの中で使う一時的な値として受け取っている感覚です。

鬼滅の刃で考える参照の受け渡し

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

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

たとえば、String swordsmanName = "竈門炭治郎"; という変数があるとします。
swordsmanName は、文字列オブジェクトを指しています。

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

鬼滅の刃でたとえると、こうです。

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

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

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

基本型とクラス型の引数を確認する

ここでは、「クラス型変数でオブジェクトを渡す方法」で使った Sample8.java を使って、基本型とクラス型の引数を確認します。

ファイル名:Sample8.java

class CorpsSwordsman
{
    private int strength;
    private double breathingPower;
    private String name;

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

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

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

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

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

        tanjiro.show();

        int strength = 9000;
        double breathingPower = 5000.0;
        String swordsmanName = "竈門炭治郎";

        tanjiro.setSwordsman(strength, breathingPower);
        tanjiro.setName(swordsmanName);

        tanjiro.show();
    }
}

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

tanjiro.setSwordsman(strength, breathingPower);

strength は int 型です。
breathingPower は double 型です。

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

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

tanjiro.setName(swordsmanName);

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

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

setSwordsman() は基本型を渡す例

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

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

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

int strength = 9000;
double breathingPower = 5000.0;

tanjiro.setSwordsman(strength, breathingPower);

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

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

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

鬼滅の刃でたとえると、strength = 9000 と breathingPower = 5000.0 は、本部にある元の記録です。
setSwordsman() には、その数値を書き写した紙が渡されます。

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

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

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

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

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

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

鬼滅の刃でたとえると、元の隊士記録台帳と、メソッド側に渡された写しの紙は別物です。
写しの紙にメモを加えても、元の台帳が自動で書き換わるわけではありません。

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

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

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

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

String swordsmanName = "竈門炭治郎";

tanjiro.setName(swordsmanName);

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

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

呼び出し元呼び出し先渡されるもの
swordsmanNamenm"竈門炭治郎" を指す参照のコピー

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

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

鬼滅の刃でたとえると、竈門炭治郎 と書かれた名前札が1つあります。
swordsmanName も nm も、その同じ名前札を指している状態です。

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

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

name = nm;

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

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

変数、フィールド指しているもの
swordsmanName"竈門炭治郎" の String オブジェクト
nm同じ "竈門炭治郎" の String オブジェクト
name同じ "竈門炭治郎" の String オブジェクト

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

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

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

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

この図が示していること

この図では、基本型の引数とクラス型の引数で、渡されるものの違いを表しています。

上段では、strength の 9000 と breathingPower の 5000.0 が、仮引数 s と b にコピーされています。
これは基本型の値渡しです。
呼び出し元の変数と、呼び出し先の仮引数は別の変数として扱われます。

下段では、swordsmanName が指している String オブジェクトへの参照の値が、仮引数 nm にコピーされています。
そのため、swordsmanName と nm は、同じ "竈門炭治郎" の String オブジェクトを指しています。

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

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

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

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

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

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

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

鬼滅の刃でたとえると、炭治郎本人を2人に増やしているわけではありません。
「この名前札を見てください」と同じ対象を指す情報を渡しているだけです。

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

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

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

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

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

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

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

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

鬼滅の刃でたとえると、同じ隊士記録を見ている状態で、その記録内容を書き換えれば、他の札から見ても変更が分かります。
しかし、受け取った札 nm を別の名前札に差し替えても、呼び出し元の swordsmanName の札まで勝手に差し替わるわけではありません。

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

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

学習では、基本型は値渡し、クラス型は参照渡しという言い方をすることがあります。
ただし、Javaの厳密な説明では、すべて値渡しです。

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

それは、参照の値です。

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

つまり、基本型もクラス型も、メソッドへ渡されるときには値がコピーされます。
違うのは、その値が「数値そのもの」なのか、「オブジェクトを指す参照」なのかです。

鬼滅の刃でたとえると、基本型では数字の紙そのものをコピーして渡します。
クラス型では、隊士や名前札へつながる札の情報をコピーして渡します。

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

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

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

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

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

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

鬼滅の刃でたとえると、数字の紙を渡したのか、隊士記録につながる札を渡したのかを区別できるようになることです。

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

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

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

この図が示していること

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

左側では、strength の 9000 が仮引数 s にコピーされています。
メソッド側で s を 12000 に変えても、呼び出し元の strength は 9000 のままです。
これは、基本型では値のコピーが渡されるからです。

右側では、swordsmanName が持つ参照の値が nm にコピーされています。
そのため、swordsmanName と nm は同じ String オブジェクトを指しています。
オブジェクト本体が増えたわけではありません。

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

Sample8.java の実行の意味

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

初期状態では、剣技の強さは 0、呼吸の力は 0.0、名前は 名無しの隊士 です。

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

int strength = 9000;
double breathingPower = 5000.0;
String swordsmanName = "竈門炭治郎";

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

次に、setSwordsman() で数値を渡します。

tanjiro.setSwordsman(strength, breathingPower);

ここでは、9000 と 5000.0 の値がコピーされ、メソッド側の s と b に渡されます。
その s と b を使って、tanjiro オブジェクトのフィールド strength と breathingPower が設定されます。

次に、setName() で名前を渡します。

tanjiro.setName(swordsmanName);

ここでは、swordsmanName が持つ参照の値が、仮引数 nm にコピーされます。
そして、name = nm によって、tanjiro オブジェクトの name フィールドも同じ String オブジェクトを指すようになります。

最後に show() で表示すると、設定された値が確認できます。

フィールド設定後の値
strength9000
breathingPower5000.0
name竈門炭治郎

この流れの中で、基本型とクラス型の両方の受け渡しが行われています。

基本型とクラス型の違いを表で整理

ここまでの内容を、もう一度表で整理します。

観点基本型クラス型
int, doubleString, CorpsSwordsman
変数が持つもの値そのものオブジェクトへの参照
引数に渡されるもの値のコピー参照の値のコピー
オブジェクト本体関係しないコピーされない
呼び出し先で扱うものコピーされた値同じ参照先を指す情報
鬼滅の刃でたとえると数字の紙を渡す隊士や名前札を指す札を渡す

この違いが分かると、メソッド呼び出しの読み方が大きく変わります。

コードを読むときに、

これは基本型だから値のコピーだな。
これはクラス型だから参照の値が渡っているな。
この処理はオブジェクト本体を増やしているわけではないな。

と判断できるようになります。

よくある誤解

値渡しと参照の受け渡しでは、次のような誤解が起きやすいです。

誤解正しい考え方
Javaには完全な参照渡しがあるJavaは厳密にはすべて値渡し
クラス型を渡すとオブジェクト本体がコピーされるコピーされるのは参照の値
基本型の仮引数を変えると呼び出し元も変わる基本型は値のコピーなので元は変わらない
クラス型なら必ず呼び出し元の変数自体が変わる仮引数の参照を入れ替えても呼び出し元の変数は変わらない
参照を渡すとは実体を渡すこと実体ではなく実体を指す情報を渡すこと

特に大切なのは、Javaはすべて値渡しという点です。

基本型では数値の値が渡されます。
クラス型では参照の値が渡されます。

このように整理すると、値渡しと参照渡しという言葉で混乱しにくくなります。

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

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

ポイント内容
基本型を引数にすると値がコピーされるint や double は値そのものが渡る
クラス型を引数にすると参照の値がコピーされるString などはオブジェクトを指す情報が渡る
オブジェクト本体は自動でコピーされない同じオブジェクトを指すことがある
仮引数は呼び出し先の変数実引数そのものではない
Javaは厳密にはすべて値渡しクラス型では参照の値が渡される
変更の影響は何を変えたかで決まる値のコピーか、参照先の中身かを見分ける

鬼滅の刃でたとえるなら、基本型の値渡しは、剣技の強さ 9000 と書かれた紙を渡すことです。
渡された紙を相手が書き換えても、もとの台帳は変わりません。

クラス型の受け渡しは、竈門炭治郎 という名前札や隊士情報を指す札を渡すことです。
札を受け取った側も、同じ対象を見られるようになります。
ただし、渡っているのは対象そのものではなく、対象を指す参照の値です。

この感覚がつかめると、メソッドに引数を渡したときに、何がコピーされ、何が共有されているように見えるのかを正しく読めるようになります。
クラス型変数と基本型変数の違いを理解するうえで、とても重要な土台になります。