前置き
Java において、メソッドへの引数はプリミティブ型だろうが参照型だろうが値渡しで渡されるのですが、いつまでたっても参照渡しという人が後をたちません。悲しい。
その理由
「参照渡し」と主張する人は、以下の二点のどちらか(あるいは両方)について誤解していることが多いように思われます。
- Java における「参照」とは何かがわかっていない
- そもそも「値渡し」と「参照渡し」が何なのかわかっていない
なので、その二点について説明してみます。
説明
Java における「参照」とは
JLS - 4.3.1 より
An object is a class instance or an array.
The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.
というわけで、Java で「参照 (references)」といったら「参照値 (reference values)」という「値」のことです。「参照値」という「値」の正体はオブジェクトへのポインタです。ポインタなのであって、他の言語、例えば C++ などで言うところの「参照」とは全く別物なのです。
でもって、参照型の変数に入っているものもこの「参照値」という「値」なわけです。
「値渡し」「参照渡し」とは
- 値渡し (call by value) : メソッドの引数に、新たに値のコピーを作って渡す方法のこと。
- 参照渡し (call by reference) : メソッドの引数に、変数そのものの参照を渡す方法のこと。
対象を「どうやって渡すか」を表しているのが「値渡し」や「参照渡し」です。何が渡されるかではないのです。つまり「値」を渡すから「値渡し」で「参照」を渡すから「参照渡し」というわけでは断じてないのです。
そして「参照の値渡し」
このような Java にかかわる「参照」という用語の使われ方に混乱した人たちや、混乱を治めようとする人たちが、「参照の値渡し」なる用語を開発しています。
しかし、「参照の値渡し」という人たちの、「参照の値渡し」にという用語における『参照』とはどのようなものを想定しているのかを考えるにつけ、Java において『「参照」といえば「参照値」という「値」』という前提をむしろ理解しにくくしているような気がします。
結局
Java において、参照型の場合でもメソッドへの引数は『 「参照値」という「値」を「値渡し」 』してるだけ。ただそれだけです。
ケーススタディ
「事実上参照渡し」説!?
「参照値」は値渡しされているわけですが、「参照値」が指し示すオブジェクトは別にこの話と関係のないところにいて、どこからか「参照値」によって参照されるのを待っています。
ですので
private void method(ArrayList<String> arg){
arg.add("PHP");
}
ArrayList<String> list = new ArrayList<String>();
list.add("Java");
method(list);
System.out.println(list); // [Java, PHP]
もちろん上記のような動作をします。
ところがこれを「事実上参照渡しのようなものだし(便宜上?)参照渡しと呼んじゃえばいいじゃん」と言い出す人がいるわけです。が、そういう人はたいてい
private void method(ArrayList<String> arg){
arg = new ArrayList<String>();
arg.add("PHP");
}
ArrayList<String> list = new ArrayList<String>();
list.add("Java");
method(list);
System.out.println(list); // [Java]
この動作は説明してくれません。
本当に参照渡しだとしたら、結果は "[Java]"
ではなく "[PHP]"
にならなくてはおかしいのですがね。
「参照型は参照渡しだけど、String 型だけは値渡し」説!?
参照渡し説を取る人たちの中でも、「String 型は参照型だけど呼び出し元で値を変えられないから、String 型だけは値渡し」という派閥があるようです。
しかし、これは単に String 型のインスタンスはそもそも immutable な(状態を変えられない)オブジェクトである、というだけの話です。immutable ですから、いついかなる場所でもどんな方法でも String 型のインスタンスの値は誰にも変えられないのです。メソッドの引数だから値が変えられなくなったわけではなく、常に変えられないのです。
なお、もちろん String 型以外の immutable なクラスでも同様なのですが、私が観測してきた限りにおいてはこの文脈で言及されるのは String 型だけのようです。他のクラスに比べて使用頻度が段違いに高いことと、String 型は言語仕様レベルで特別扱いされているためいろいろ混乱を招きやすいことが原因、なのでしょうかね?
C# との比較
C# という、Java によく似ているいた言語があります。
少なくともメソッド回りの仕様はだいたい Java と同じです。基本的には C# も値渡しになります。Java と同じです。
private void TestMethod(List<String> arg){
arg = new List<String>();
arg.Add("C Sharp");
}
List<String> list = new List<String>();
list.Add("Java");
TestMethod(list);
System.Console.WriteLine(list[0]); // "Java"
C# が Java と違うのは、ref
というキーワードを持っていることです。これを使うと「本当の」参照渡しになります。
private void TestMethod(ref List<String> arg){
arg = new List<String>();
arg.Add("C Sharp");
}
List<String> list = new List<String>();
list.Add("Java");
TestMethod(ref list);
System.Console.WriteLine(list[0]); // "C Sharp"
C# はこのような言語仕様を持っていますので、C# においてデフォルトの動作を「参照渡し」という人はさすがにいないでしょう。ref
をつけた時が「参照渡し」なのだから、ついてない時は少なくとも『「参照渡し」ではない何か』ですよね。
そして、C# が持っている参照渡しをするための機能 ref
を持っていない Java は、つまり「参照渡し」を持っていないということになります。いささか回りくどい証明ではありますが。
余談
Java には参照で渡す機能がないので、参照渡しだとどうなるかを、「実際に動くJava のコード」 で説明することができません。もちろん、それはそうとしか言いようがないのですが、挙動を説明するにあたっては案外面倒だとこの記事を書いていて気づいたのでした。
(以下 2018-12-16 追記)
お話させていただきました
JJUG CCC 2018 Fall にて、この記事の内容+αをもう参照渡しとは言わせない 2018 冬というタイトルでお話させていただきました。
その時の資料は以下になります。そちらもぜひご参照ください。