「Javaの参照渡しって、なんだかよく分からない…」
「値渡しとの違いが曖昧で、コードを書いていて不安になる」
「面接で『Javaは参照渡しですか?』と聞かれたら、どう答えれば良いのだろう?」
何を隠そう、私自身も学習を始めたころは、Javaの「参照渡し」という言葉に何度も頭を悩ませました。メソッドにオブジェクトを渡したら中身が変わったので、「これが参照渡しか!」と勘違いしていた時期もあります。
この記事は、過去の私と同じようにJavaの引数の渡し方で混乱している方に向けて書きました。
この記事を読み終えるころには、次の状態になっているはずです。
- Javaの引数渡しが「値渡し」であると、自信を持って説明できる
- プリミティブ型とオブジェクト型で挙動が違う理由を、明確に理解できる
- 「java 参照渡し」と検索する日々から卒業し、実務で迷わずコードを書ける
Javaの基本でありながら、多くの人がつまずくこのテーマを、一緒に完全に理解していきましょう。
そもそも「参照渡し」とは?

Javaの話に入る前に、まずはプログラミングの一般的な概念として「参照渡し」と「値渡し」について理解しておく必要があります。この2つの違いを知ることが、Javaの理解への第一歩です。
値渡しとの違いを簡単に説明
値渡し (Call by Value) とは、メソッドに引数を渡すときに、値そのもののコピーを渡す方法です。
例えるなら、友人に大事な書類の「コピー」を渡すようなものです。友人がそのコピーに何か書き込んでも、あなたの手元にある原本の書類には何の影響もありません。これが値渡しです。メソッドの中で引数の値をいくら変更しても、呼び出し元の変数は変わらないのが特徴になります。
一方で、参照渡し (Call by Reference) は、メソッドに引数を渡すときに、値が格納されている場所の情報(アドレス)を渡す方法です。
こちらは、友人に書類が保管されている「金庫の鍵」を渡すイメージです。友人がその鍵を使って金庫を開け、中の書類に追記すれば、あなたが後で同じ金庫を開けたとき、書類は書き換えられています。メソッドの中で引数を変更すると、呼び出し元の変数も影響を受ける、これが参照渡しの特徴です。
Javaでは「参照渡し」は存在するのか?

ここで本題です。それでは、私たちの使うJavaには「参照渡し」という仕組みが存在するのでしょうか。
先に結論からお伝えします。
Javaに、厳密な意味での「参照渡し」は存在しません。Javaの引数渡しは、すべて「値渡し」です。
「え、でもオブジェクトを渡すとメソッド内で変更できるじゃないか!」と思われたかもしれません。その感覚は鋭いですが、Javaの仕組みを正しく理解するためにもう一歩だけ踏み込んでみましょう。
Javaは「参照の値渡し」である
Javaはすべて値渡しである、と説明しました。しかし、オブジェクトを引数として渡した際の挙動は、一見すると参照渡しのように見えてしまいます。この現象を理解するカギが、「参照の値渡し(Call by Value of Reference)」という考え方です。
オブジェクト変数が持っているのは、オブジェクトそのものではなく、メモリ上のどこにオブジェクトが格納されているかを示す「住所」のようなものです。この「住所」のことを参照値や単に参照と呼びます。
Javaでオブジェクトをメソッドに渡すとき、オブジェクト本体や「金庫の鍵」そのものではなく、この「住所」が書かれた紙のコピーを渡しているのです。
住所のコピーなので、それを使って元のオブジェクトの場所(家)にたどり着き、中身(家具の配置)を変えることはできます。しかし、渡されたのはあくまで住所のコピー用紙なので、その紙に別の住所を書き込んでも、元の住所が書かれた紙には何の影響もありません。
この「参照(住所)を値渡し(コピーして渡す)」する仕組みこそが、Javaの引数渡しの本質なのです。
Javaにおける実際の「渡し方」

では、もう少し具体的に、Javaがどのように引数を扱っているのかを見ていきましょう。特に「プリミティブ型」と「オブジェクト型」でどのような違いが生まれるのかが重要なポイントです。
オブジェクトを引数に渡すとどうなる?
前述のとおり、オブジェクトを引数に渡すと、そのオブジェクトの参照値(アドレス)がコピーされてメソッドに渡されます。
メソッド側で受け取った引数(コピーされた参照値)を使って、オブジェクトのフィールド(プロパティ)にアクセスし、値を変更したとします。このとき、参照元は同じオブジェクトを指し示しているため、呼び出し元のオブジェクトにも変更が反映されます。
これが、「Javaは参照渡しだ」と勘違いされやすい最大の理由です。しかし、重要なのは、メソッド内で引数に新しいオブジェクトを再代入した場合です。この場合、コピーされた参照値が新しいオブジェクトのアドレスを指すようになるだけで、呼び出し元の変数が指し示すアドレスは変わりません。したがって、呼び出し元のオブジェクトは何も影響を受けないのです。
プリミティブ型との違い
一方で、int
やdouble
、boolean
といったプリミティブ型の場合は非常にシンプルです。
プリミティブ型の変数は、参照値ではなく値そのものを直接保持しています。そのため、メソッドに引数として渡されるときも、値そのものがコピーされて渡されます。
これはまさに、最初に説明した「値渡し」の例そのものです。メソッドの中でいくら引数の値を変更しても、それはあくまでコピーされた値に対する操作です。呼び出し元の変数には一切影響がありません。Java11などのバージョンに関わらず、この基本原則は変わりません。
実例で理解する「参照渡し」

言葉での説明だけでは、まだ少しイメージがしにくいかもしれません。ここからは具体的なコードを見ながら、Javaの引数渡しの挙動を確認していきましょう。
プリミティブ型を渡した場合の挙動
まずは、int
型の変数をメソッドに渡すシンプルな例です。
public class Main {
public static void main(String[] args) {
int number = 10;
System.out.println("メソッド呼び出し前の値: " + number); // -> 10
addOne(number);
System.out.println("メソッド呼び出し後の値: " + number); // -> 10
}
public static void addOne(int num) {
num = num + 1;
System.out.println("メソッド内の値: " + num); // -> 11
}
}
実行結果:
メソッド呼び出し前の値: 10
メソッド内の値: 11
メソッド呼び出し後の値: 10
addOne
メソッドの中でnum
の値を11
に変更しましたが、呼び出し元であるmain
メソッドのnumber
変数の値は10
のままです。これは、number
の値10
がコピーされてaddOne
メソッドに渡されたためです。num
とnumber
は完全に別の変数として扱われています。
オブジェクトを渡した場合の挙動
次に、オブジェクトを渡す場合の挙動を見てみます。ここでは、名前を保持するUser
クラスを例にします。
class User {
String name;
public User(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
User user = new User("Taro");
System.out.println("メソッド呼び出し前の名前: " + user.name); // -> Taro
changeName(user);
System.out.println("メソッド呼び出し後の名前: " + user.name); // -> Jiro
}
public static void changeName(User u) {
u.name = "Jiro";
}
}
実行結果:
メソッド呼び出し前の名前: Taro
メソッド呼び出し後の名前: Jiro
今度は、changeName
メソッドで名前を変更した結果が、呼び出し元のuser
オブジェクトにも反映されました。これは、user
変数が持つ参照値(アドレス)がコピーされてu
に渡され、u
とuser
が同じUser
オブジェクトを指しているためです。
では、メソッド内で新しいオブジェクトを代入するとどうなるでしょうか。
// ... Userクラスは同じ ...
public class Main {
public static void main(String[] args) {
User user = new User("Taro");
System.out.println("メソッド呼び出し前の参照: " + user);
reassignUser(user);
System.out.println("メソッド呼び出し後の参照: " + user);
System.out.println("メソッド呼び出し後の名前: " + user.name);
}
public static void reassignUser(User u) {
u = new User("Hanako"); // 新しいオブジェクトを代入
System.out.println("メソッド内の参照: " + u);
}
}
実行結果 (参照値は環境により異なります):
メソッド呼び出し前の参照: User@15db9742
メソッド内の参照: User@6d06d69c
メソッド呼び出し後の参照: User@15db9742
メソッド呼び出し後の名前: Taro
この結果が「Javaは値渡し」であることの決定的な証拠です。reassignUser
メソッドの中で引数u
に新しいUser
オブジェクトを代入しても、それはあくまでコピーされた参照値を書き換えているだけです。呼び出し元のuser
変数が持つ参照値は全く変わっておらず、結果としてuser
が指すオブジェクトの名前も"Taro"のままです。
配列を渡したときの注意点
配列もJavaではオブジェクトの一種として扱われます。そのため、メソッドに引数として渡した場合の挙動は、基本的にオブジェクトと同じです。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] numbers = {1, 2, 3};
System.out.println("メソッド呼び出し前の配列: " + Arrays.toString(numbers)); // -> [1, 2, 3]
changeFirstElement(numbers);
System.out.println("メソッド呼び出し後の配列: " + Arrays.toString(numbers)); // -> [99, 2, 3]
}
public static void changeFirstElement(int[] arr) {
if (arr != null && arr.length > 0) {
arr[0] = 99;
}
}
}
実行結果:
メソッド呼び出し前の配列: [1, 2, 3]
メソッド呼び出し後の配列: [99, 2, 3]
このように、メソッド内で配列の要素を変更すると、呼び出し元の配列にも変更が反映されます。これも、配列オブジェクトへの参照値がコピーされて渡されているためです。
「参照渡し」と勘違いしやすいポイント

ここまでで、Javaの引数渡しの仕組みは「参照の値渡し」であることを解説しました。では、なぜこれほど多くの人が「参照渡し」という言葉で混乱してしまうのでしょうか。
なぜ多くの人が混乱するのか?
最大の理由は、オブジェクトを引数に渡したときに「メソッド内で加えた変更が呼び出し元に反映される」という挙動が、見た目上、参照渡しと全く同じように見えるからです。
user.name = "Jiro"
のようにオブジェクトの「中身」を変更する操作と、user = new User("Hanako")
のように変数に別のものを「代入」する操作の違いを、意識的に区別できていないことが混乱の根本的な原因といえます。
プログラミング学習では、まず動くものを作ることが優先されがちです。そのため、メモリレベルで何が起きているのかを深く考えずに進んでしまい、この違いに気づきにくいのです。
「参照のコピー」という表現の落とし穴
「参照渡し」という言葉が間違いであることは理解できても、「参照のコピー」という表現もまた、少し注意が必要です。この表現は正しいのですが、「参照」という言葉が入っているため、どうしても「参照渡し」のイメージに引きずられてしまいがちです。
最も正確で誤解のない理解は、「Javaはすべて値渡しである。オブジェクトの場合は、その参照値という『値』が渡される」と覚えることです。常に「値渡し」を基本に考えることで、プリミティブ型とオブジェクト型の違いもスムーズに理解できます。
他言語(C++やPython)との違い
混乱の原因の一つに、他のプログラミング言語での経験が影響している場合もあります。
例えば、C++ にはポインタや&
を使った明確な「参照渡し」の仕組みが存在します。C++経験者がJavaを学ぶと、似たような挙動から「これも参照渡しだろう」と類推してしまうことがあります。
また、Python の引数渡しは「共有による呼び出し(call by sharing)」と呼ばれます。これはJavaの仕組みと非常に似ていますが、用語が異なるため、複数の言語を学ぶ中で知識が混ざってしまうことも考えられます。
言語ごとに引数渡しの厳密な定義や仕組みは異なります。Javaを扱う際は、「Javaの世界ではすべて値渡し」と割り切って考えることが大切です。
まとめ:Javaの引数渡しを正しく理解しよう
今回は、多くのJava学習者がつまずく「参照渡し」の概念について、その真相を解説しました。複雑に感じるかもしれませんが、一度「すべて値渡し」という原則を理解してしまえば、コードの挙動に迷うことは格段に減るはずです。
理解のポイントを3行で復習
- Javaの引数渡しは、例外なくすべて「値渡し」です。
- プリミティブ型は値のコピー、オブジェクト型は参照値(アドレス)のコピーが渡されます。
- そのため、メソッド内でオブジェクトの中身は変更できますが、参照先そのものは変更できません。
面接や実務で混乱しないための考え方
もし面接で「Javaに参照渡しはありますか?」と質問されたら、自信を持ってこう答えましょう。
「いいえ、Javaの引数渡しはすべて値渡しです。ただし、オブジェクトを引数にする場合は、オブジェクトの所在地を示す参照値がコピーされて渡されるため、メソッド内でそのオブジェクトの状態を変更することが可能です。この挙動から参照渡しと混同されることがありますが、仕組みとしては値渡しです。」
ここまで答えられれば、Javaのメモリモデルをきちんと理解しているエンジニアとして、高く評価されるでしょう。