Javaにある"値渡しと参照渡しの境界みたいな部分"の話
TL;DR
Twitterベースのプログラミング初心者向け勉強会で、ちょっと雑というか、きちんと説明しないと後々、誤解が出そうな話題があったので、纏めます。
何の話かというと「プログラミングにおける関数の使い方で、値渡しと、参照渡しの違い」みたいな部分。
Javaはすべて値渡しです:はい、究極的には正しいです。ただし、落とし穴になりがちな部分を知らないと事故ります。
Javaは複写できる型は値渡し、複写できない型は参照渡し:そんな風に見えるけど違います。
もうちょっと詳しく
テストしてみてください
こんなコードを想定してください。さて、実行結果はどうなるでしょうか。
import java.util.*;
public class Main
{
public static void main(String[] args) throws Exception
{
Integer i = 1;
sub(i);
System.out.println(i.toString());
}
private static void sub(Integer i)
{
i = 10;
return;
}
}
出力されるのは
です。典型的な「関数に値渡しをした」挙動ですね。
では次に、こんなコードだとどうなるでしょうか。
import java.util.*;
public class Main
{
public static void main(String[] args) throws Exception
{
List<String> li = new ArrayList<>();
li.add("1番目");
sub(li);
for(String s : li)
{
System.out.println(s);
}
}
private static void sub(List<String> li)
{
li.add("2番目");
}
}
出力結果は
はい。参照渡しに見える挙動が起きています。
これで「List<String>は複製できないから参照渡しなんだ!」と思ったら、ドツボにはまります。
次のケース。これはどうでしょうか?
import java.util.*;
public class Main
{
public static void main(String[] args) throws Exception
{
List<String> li = new ArrayList<>();
li.add("1番目");
sub(li);
for(String s : li)
{
System.out.println(s);
}
}
private static void sub(List<String> li)
{
li = new ArrayList<>();
li.add("2番目");
}
}
もしsub関数に、List<String>が本当に参照渡しされているなら、subの中で
li = new ArrayList<>();
li.add("2番目");
しているので、「2番目」だけが出力されないとおかしいです。
ですが、実行結果は、
はい。THE・値渡し みたいな挙動です。つーか値渡しですね。
どうしてこうなるの
「値をコピーできる型は、値渡ししてる」
まず、これはそのままの理解でOKです。つまり、値をメモリ上の別の場所にそのまま複写して、それを関数の引数にしてる(渡してる)わけです。
「値をコピーできない型は、値のあるメモリ上の場所を、値渡ししてる」
ここがこの話の肝です。専門用語でポインタというやつです。場所といっても、実際には数値(使えるメモリ領域の中でn番目の場所、的なニュアンス)です。
つまりInteger等の数値型のように、自由に複写できます。ゆえに、「場所の情報を複写して渡す」ことが可能になります。
そして受け取った関数側で、どう処理されるかといえば、
直接的な操作は、複写されたメモリ上の値をそのまま参照しに行くので、呼び出し元から渡されたオブジェクトに影響する(上のサンプルの例でいえば追加される)
つまり「参照渡し」されたような動作をする
内容そのものを書き換える操作は、複写された「どこの場所か」の値が指定している場所にある変数の情報を、ざっくり書き換える。でもそれは「複写された」場所の情報なので、呼び出し元には影響しない。
つまり「値渡し」されたのと同じ動作をする
というからくりです。OK?
わからなかったら、わかるまでコード書いて、動かし倒して理解してください。習うより慣れろ。
それはそれとして苦言
そもそも、この記事を書いたきっかけは、冒頭に書いた、プログラミング勉強会関連なのです。が。
今回書いた部分って、割とJava特有で、ほかの言語ではあまり似たような動作は見かけないと思います。
実際、オブジェクト型だけ参照渡しになるような言語も普通にあるので、混乱することもあるでしょう。
そういうこともあるので、特にプログラミング初心者は、特定の言語に依存しない、プログラミングの一般論みたいな解説をベースに学んでしまうと、「その言語特有の挙動」に振り回されることがあります。
でもまあ、これって、本来は「特定の言語に特化した教材」を使うことで、100%とはいかなくても、かなり解消できるのです。
言語特有の部分に注釈をいれることもできるし、そもそも一般論に偏る必要がないので。
という前提を加味して、あえて言います。
カスであると。
間違えました。あえて言います。
一般論で教えるなら、個別の言語を学んでる人を、それぞれの言語に特化した部分でフォローできる体制を作らないと、逆に躓く原因になるよ。と。
やるなとは言いませんが、無責任なやり方では、初心者を崖から突き落とすだけにしかなりません。
というか、目的と手段を混同したり、目的の正しさで間違った手段を正当化してると、技術者はどんどん信用を失います。よ?
この記事が気に入ったらサポートをしてみませんか?