はじめに
こんにちは。
プログラミング初心者Wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。
十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
間違い等あれば、指摘いただけると助かります。
対象読者
- Javaを勉強中の方
- Java SE Bronze試験を勉強中の方
- Javaの「値渡しと参照の値渡し」「Stringクラス固有の特徴」についてざっくり知りたい方
目次
1. 値渡しと参照の値渡し
2. プリミティブ型の「値渡し」
3. 参照型の「参照の値渡し」
4. Stringクラス固有の挙動
本文
1. 値渡しと参照の値渡し
Javaのデータ型は、「プリミティブ型」と「参照型」に分けられます。
プリミティブ型は「基本データ型」とも呼ばれ、「数値型」「文字型」「真偽値型」などのことです。参照型は、「配列型」「オブジェクト型」「列挙型」などのことです。
プリミティブ型と参照型の違いは、変数に格納されているものにあります。
プリミティブ型では、変数に代入された「値そのもの」が格納されています。一方参照型では、変数に代入された値が格納されている場所の「参照アドレス(ポインタ等)」が格納されています。
シンプルに値を代入して取り出すだけなら、プリミティブ型と参照型はほとんど違いがないように見えます。しかし、変数を別の変数に代入した場合などで挙動が大きく異なります。
具体的には、プリミティブ型では値そのものをコピーする「値渡し」が、
参照型では値を格納した参照アドレスをコピーする「参照の値渡し」が行われるのです。「値渡し」か「参照の値渡し」かによって、代入元の変数の挙動が変わります。
2. プリミティブ型の「値渡し」
実際にどのような挙動になるのか、コードで確認してみましょう。
まずは、「値渡し」のプリミティブ型です。
int a = 100;
int b = a; //変数aを変数bに代入
b = 95; //変数bの値を書き換える
System.out.println(a); //結果:100 代入元の変数に変化なし
プリミティブ型は「値渡し」です。変数aを別の変数bに代入した場合、格納された値そのものが代入先にコピーされます。代入元変数と代入先変数は同じ値ですが、両者に繋がりはありません。そのため、代入先変数で値を変えても、代入元変数に影響しません。
3. 参照型の「参照の値渡し」
次に、「参照の値渡し」の参照型です。
int[] array = {100, 200, 350};
int[] array2 = array; // 配列arrayを配列array2に代入
array2[0] = 99; // 配列array2の値を書き換える
System.out.println(array[0]); // 結果:99 代入元の変数まで変化している
参照型は「参照の値渡し」です。変数arrayを別の変数array2に代入した場合、データを格納した参照アドレスがコピーされます。格納されているアドレスが同じということは、代入元変数と代入先変数は同じデータを参照していることになります。そのため、代入先変数でデータを書き換えたら、同じデータを参照している代入元変数まで変わってしまうのです。
参照型において、代入元変数まで変わってしまう挙動を、「参照の値渡しの副作用」と呼びます。
4. Stringクラス固有の挙動
Stringは、プリミティブ型ではなく、参照型のクラスです。コンピュータ内部では、char配列型として処理されています。
しかし、Stringクラスは固有の2つの特徴を持つため、他の参照型と挙動が異なります。
1. イミュータブル
Stringクラスの1つ目の特徴は「イミュータブル」です。「イミュータブル(不変)」とは、インスタンスを生成後に値を変更できない性質のことです。
下のコードをご覧ください。
String s = "abc"; //①
s = "def"; //②
System.out.println(s); //結果:def
まるで変数sの値が"abc"から"def"に書き換わる「ミュータブル(書き換え可能)」な挙動に見えますが、コンピュータ内部の挙動は違います。
内部の処理をみてみましょう。
①Stringクラス変数が宣言され、文字列リテラル(「"」に囲まれたもの)"abc"をデータとして受け取る。"abc"のデータはインスタンスに格納され、その参照アドレスが変数sに格納される。
②変数sが新しいデータ"def"を受け取る。"def"のデータはインスタンスに格納され、新しい参照アドレスが変数sに格納される。
つまり、Stringクラスに新しいデータを代入すると、代入前のデータへの「参照アドレス」が新しいデータのへの「参照アドレス」に置き換わっているのです。
このイミュータブルな性質によって、Stringクラスは参照型でありつつ、参照の値渡しの副作用を回避しています。
下のコードをご覧ください。
String s1 = "abc"; //③
String s2 = s1; //④
s2 = "def"; //⑤
System.out.println(s1); //結果:abc
System.out.println(s2); //結果:def
④で変数s1を変数s2に代入後、変数s2に新しいデータを代入しています。
参照型の挙動を考えると、代入元のs1の値も変化しているかと思えば、そうではありません。
内部の処理をみてみましょう。
③ 変数s1に"abc"のデータの参照が格納される
④ 変数s2に変数s1に格納されているインスタンスの参照アドレスがコピーされる。ここまでは、通常の参照型を同じ挙動
⑤ 変数s2に新しく代入されたデータ"def"のインスタンスの参照アドレスが格納される。
参照型では、参照先のデータが書き換えられるため、代入元の値も変化していました。
しかし、Stringクラスはイミュータブルであるため、参照先のデータは書き換えられません。新しいデータを格納することで変化したのは、代入先の参照アドレスだけです。代入元の参照アドレスは変化しないため、代入元のデータも変化しないのです。
Stringクラスは、イミュータブルな性質によって、参照の値渡しの副作用を回避していているのです。
2. コンスタントプール
Stringクラスのもう一つの特徴が、「コンスタントプール」です。「コンスタントプール」とは、同じ文字列リテラルを使い回す仕組みのことです。
Stringクラスは、プログラム中でよく使われるため、文字列リテラルを記述するだけでインスタンスが生成できる仕組みになっています。便利な機能ではありますが、その反面、文字列リテラルごとにメモリを確保するためメモリの負荷が増えたり、処理に時間がかかったりという問題も生まれます。
そこで、メモリ消費や処理負荷を抑えるために、同じ文字列リテラルによって生成されたインスタンスを自動的に使い回す仕組みが生まれました。
下のコードをご覧ください。
String s3 = "abcd";
String s4 = "abcd";
System.out.println(s3 == s4); //結果:true
変数s3と変数s4に同じ値を格納しています。s3とs4は別に宣言したインスタンスなので、本来別のインスタンスのはずです。
その変数s3と変数s4を等値「==」で比べます。等値は、参照先のデータではなく、参照アドレスが同じかどうかを比べます。変数s3と変数s4は別のインスタンスなので、参照アドレスは異なり、結果はfalseとなるはずです。ところが、結果を見るとtrueになっています。
これは、同じ文字列リテラルを使い回すコンスタントプールの結果です。同じ文字列リテラルが複数ある場合、2度目以降は新しいインスタンスを生成せずに、先に生成したインスタンスの参照アドレスを使い回します。
変数s3と変数s4も本来は別のインスタンスですが、コンスタントプールの結果、同じインスタンスへの参照が格納されたため等値判定「==」でtrueとなったのです。
これがコンスタントプールの挙動です。
Stringクラスはイミュータブルなので、参照型のようにデータの書き換えが他のインスタンスに波及することはありません。
しかし、コンスタントプールによって本来等値判定でfalseになるはずのものが、trueとなる場合が存在します。
Stringクラスで同じかどうかを判定する際は、等値判定「==」ではなく、参照アドレスに格納しているデータを見比べる等価判定「equals」を利用した方が良いでしょう。
まとめ
- プリミティブ型と参照型では、値のコピーする際などで挙動が異なる
- 参照型では、コピー時に参照アドレスが受け渡されるため、代入先で値を変更すると、代入元の値も変化する。これを参照の値渡しの副作用という
- Stringクラスはイミュータブルな性質によって、参照の値渡しの副作用を回避している
- その一方Stringクラスは、コンスタントプールというシステムで同じ文字列リテラルを使い回しているため、注意が必要
記事は以上です。
最後までお読みいただき、ありがとうございました。
参考文献
この記事は以下の情報を参考にして執筆しました。
- [徹底攻略 Java SE Bronze 問題集]
- [スッキリわかるJava入門 第4版]
- [5.2 参照型変数とnull値~Java Basic編](最終更新 2023-11-05)(https://qiita.com/KenyaSaitoh/items/f981f19b55ae477e5915) (参照 2025-04-10)
- [16.1 文字列を表すクラスの特徴とAPI(Stringクラス、StringBuilderクラスなど)~Java Basic編](最終更新 2023-11-05)(https://qiita.com/KenyaSaitoh/items/fe85a697abcfe1e11f98) (参照 2025-04-10)