Chapter 10

Java 参照型

おでん
おでん
2021.08.14に更新

String型とは

String型は文字列を表すリテラルである、というのが一般的に解釈されると思います。
しかし、この解釈は厳密には正しくありません。
結論、Stringは参照型のクラスです。
これを紐解いていきます。

Stringクラス

上記で記述したように、Stringは参照型のクラスです。
これはJavaSEのAPIに含まれるクラスです。
JDKをインストールした際に、そのAPIもインストールされます。
クラスはメンバ変数とメソッドを持ちます。

  • Stringクラスのメンバ変数:char配列型の変数
  • Stringクラスのメソッド:char配列を操作する機能

このメンバ変数とメソッドを持ち合わせているため、擬似的に文字列を操作することができます。
つまり、以下は同じです。

String str = "abc";
char arr[] = {'a','b','c'};

Stringインスタンスの生成

Stringはクラスであるため、インスタンス化する必要があります。
インスタンス化の方法は以下の2つがあります。

1. String 変数名 = "文字列";
2. String 変数名 = new String("文字列");

おそらく、多くの方は1の文字列を「""」で囲む記述をされていると思います。
2ではなく、1が多用されるのには理由があります。
それは1の記述のほうが、メモリ使用容量が少なく済むためです。
なぜ、メモリ使用容量が少なく済むのかを説明するために、それぞれの記述の解説をします。

1. String 変数名 = "文字列";

この記述の場合は、「""」が処理を行なっています。
「””」内に指定された文字列をchar型配列として保持するStringインスタンスをconstant pool(Java仮想メモリ内のヒープ領域の一部)に生成(または参照)し、そのアドレス値を返します。
返ってきたアドレス値を変数名に代入します。
ここで重要な点が、インスタンスを生成ではなく参照をすることもあるという点です。
これは、constant pool内に同じ文字列を持つStringインスタンスが既にあれば、そのアドレス値を参照するということです。
この時点でよく分からなくても、この後図解しますので大丈夫です。

2. String 変数名 = new String("文字列");

この記述の場合は、「new演算子」が処理を行なっています。
StringインスタンスをJava仮想メモリ内のヒープ領域に新たに生成します。

それでは、以下の事例で違いを見てみましょう。

String val1 = "abc";
String val2 = "abc";

この記述の場合は、val1に新たに生成したStringインスタンスのアドレス値を代入しています。
val2に代入する文字列は同じ文字列を持つStringインスタンスが上記で生成されているので、それを参照し、アドレス値を返します。
つまり、val1とval2は同じStringインスタンスが保持する文字列を参照しています。
""

String val1 = new String("abc");
String val2 = new String("abc");

この記述の場合は、new演算子で2つのStringインスタンスを生成しています。
つまり、val1とval2は別のStringインスタンスが保持する文字列を参照しています。

new

このように、「""」の場合はすでに存在する文字列があれば、新たにメモリを使用しないことがわかりました。
これで、「""」の方がメモリ使用容量が少なく済む可能性があることにつながりました。

まとめると以下の通りです。

書式 インスタンス 生成先
"文字列" 生成または参照 constant pool内
new String("文字列") 生成 ヒープ内

それでは、例を見てみましょう。

```
String str1 = "helloworld";
String str2 = "hello";
str2 = str2 + "world";
String str3 = new String("helloworld");
String str4 = "helloworld";

System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str4);

if(str1 == str2){
System.out.println("str1 == str2");
}
else if(str1 == str3){
System.out.println("str1 == str3");
}
else if(str1 == str4){
System.out.println("str1 == str4");
}


上記の場合、str1〜str4は全て文字列「helloworld」が代入されているので、まずは4回出力されます。
それでは、下部の条件分岐はどの条件にてtrueとなるでしょうか。
結果は以下のように出力されます。

``` a:結果
helloworld
helloworld
helloworld
helloworld
str1 == str4

str1〜str4は全て文字列「helloworld」が代入されていますが、「if(str1 == str2)」と「else if(str1 == str3)」はfalseとなっていることが分かります。
これは説明した通り、String型は参照型であり、代入されているのは文字列ではなくStringインスタンスのアドレス値であるためです。
分かりやすいように、それぞれの変数に仮のアドレス値を振ってみます。

```
String str1 = "helloworld"; //100
String str2 = "hello"; //200
str2 = str2 + "world"; //300
String str3 = new String("helloworld"); //400
String str4 = "helloworld"; //100

このように見ると、同じStringインスタンスのアドレス値が代入されているのは、str1とstr4であることが分かります。

# equalsメソッド

上記の例のように、アドレス値での比較は一見分かりづらいです。
そこで、文字列自体を比較できるメソッドが**equalsメソッド**です。
このメソッドは、文字列(実際にはchar型配列を文字列に見せかけている)の比較を行い、trueかfalseを返します。
記述書式は以下です。

Stringインスタンスの値.equals(比較対象の値)

上記の例に追記してみます。

if(str1.equals(str2)){
System.out.println("str1.equals(str2)");
}


``` a:結果
str1.equals(str2)