【Java】Stringについて（Immutable & StringPool & 文字列比較）

# はじめに
今回の記事ではJavaのStringについて深掘りしていきたいと思います。

今所属しているチームのWikiで「文字列を扱うときはStringよりStringBuilderを使おう」という記事を読んでみて、わからないことやもっと勉強したいことがあって記事に書きます。なので今回と次の記事はJavaの文字列に関して勉強した内容になります。

# Stringの特徴

### Stringはオブジェクト
プログラムは「変数」を扱う際、その変数のデータタイプを指定する。
Javaでは、データ型を大きく分けて基本データ型(primitive type)と参照型(reference type)がある。
その中`String`は**参照型**(**reference type**)である。

つまり、Stringはほかの参照型オブジェクトと同じようにStack領域ではなく、Heap領域にデータが保存されます。

```java
int age = 19;         // 基本データ型(primitive type)
String name = "kim";  // 参照型(reference type)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/abc838b8-ca72-33a1-3424-d0443bcb71a8.png)
stack領域には変数名であるnameと"kim"というデータが保存されたHeapの場所（アドレス）が保持されます。

### StringはImmutable（不変オブジェクト）
**Immutable**とは作成後にその状態を変えることのできないオブジェクトのことです。

基本的にJavaでStringで保存されたデータは変更できません。

下記のコードを見ると変数`str`が参照している`"Hi"`が+演算子で`" Kim"`を追加することで、name変数に`"Hi　Kim"`でデータが入れ替わります。しかし、実際にHeapメモリでは`"Hi Kim"`が新たに作られそのアドレスをstackのstr変数がまた参照する仕組みになっています。
```java
String name = "Hi";
name += " Kim";

System.out.pringln("name"); // Hi Kim
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/09dca5ee-7c1b-84f8-d29e-68ff00ae732e.png)
ここで`"Hi"`は参照が切れてガベージコレクションによって解放される。

nameの参照アドレスが変わったのは`hashCode()`で確認できます。
```java
String name = "Hi";

System.out.println(name.hashCode());  // 2337

name += " Kim";

System.out.println(name.hashCode());  // -2135669968
```
同じ変数のアドレスを出力するにも関わらずアドレスの値は全く違うことが分かります。

このようにStringはデータを入れ替わる際、新たにデータを作成し、そのアドレスを変数に代入することでデータを入れ替えています。
### StringがImmutableのはなぜ？
ここで疑問が浮かびます。なぜ色んなデータタイプの中で、StringだけImmutableなのか？

その理由は大きく３つあります。

+ **メモリの節約ができる**（**String Constant Pool**）

例えば下記のように各変数に同じデータを代入すると考えます。
```java
String one = "1";
String temp = "1"; 
String first = "1";
```
Javaはこの際、`"1"`のデータ3つ作ってそれぞれの変数に代入することではなく、最初1回だけ`"1"`をHeapメモリの**String Constant Pool**に保存します。そのあとほかの変数が`"1"`を使うときは**String Constant Pool**のデータを参照することでメモリの使用量を減らせます。
しかし、これには一つの問題点があります。
```java
first = "first";
```
first変数のデータを`"first"`に変えると、**String Constant Pool**の`"1"`が`"first"`に変わり、`"1"`を参照していたすべての変数のデータが変わる**Side Effect**が生じます。

幸いにJavaはStringをImmutableで扱っているため、上記のような**Side Effect**の問題はありません。

:::note warn
String Constant Poolについては[Stringのnew()演算子とリテラル](#stringのnew演算子とリテラル)で説明します。
:::

+ **セキュリティ**

例えばDBのusernameとpasswordやソケット通信でのhostとportなどの情報がStringで渡されるので、ハッカーの攻撃からデータが変わることを防げます。
+ **マルチスレッド環境でも安全**（**Synchronization**）

データがImmutableであればマルチスレッド環境で数十数百のスレッドがStringデータに参照してもデータを変えることはできないためThread Safeが保証される。

# Stringのnew()演算子とリテラル
JavaでString変数を宣言するときに`リテラル`と`new()演算子`を使う方法があります。
```java
String str1 = "リテラル"

String str2 = new String("new演算子")
```
コードで見ると宣言方法が違うだけだと思うかもしれませんが、実際には**メモリに保存**される方法も違います。

new()演算子でStringを宣言するとHeapメモリに保存されますが、リテラルでStringを宣言するとString Constant Poolに保存されます

下記のように文字列を宣言してみます
```java
String str1 = "Hi";
String str2 = "Hi";
String str3 = new String("Hi");
String str4 = new String("Hi");
```
このコードを図で表現すると次のようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/8e0580bf-15e8-96ef-46b9-40e7b2de8948.png)
**メモリの節約ができる**（**String Constant Pool**）に記述したようにstr1とstr2変数は同じString Constant Poolのアドレスを参照している。

# 文字列比較　==、equals()

Javaでは`int`と`boolean`などの基本データタイプは`==`演算子で比較します。

しかし、Javaを勉強してみるとStringのようなオブジェクトを比較するときは`==`ではなく、`equals()`というメソッドを使うようになったいます。

コードでStringを比較してみます。
```java
// リテラル
String str1 = "Hi";
String str2 = "Hi";
// new()演算子
String str3 = new String("Hi");
String str4 = new String("Hi");

// リテラル - リテラル 比較
System.out.println(str1==str2);  // true

// リテラル - new()演算子 比較
System.out.println(str1==str3);  // false 
System.out.println(str1.equals(str3));  // true

// new()演算子 - new()演算子 比較
System.out.println(str3==str4);  // false
System.out.println(str3.equals(str4));  // true
```
このような結果が出たのは`==`演算子と`equals()`メソッドの差を見てみると、
* `==`演算子は比較する二つの対象のアドレスを比較する。
* `equals()`メソッドは比較する二つの対象のデータそのものを比較する。

`System.out.println(str1==str2)`は同じString Constant Poolに保存されているデータを参照するため、trueを返す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/e39e18bc-89fe-54bd-dece-469322f3f093.png)

一方で`System.out.println(str3==str4)`はHeapメモリでそれぞれ別のメモリ領域に保存されてデータを参照するため、falseを返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/ccc41162-426f-8dff-20e6-dd0d58adc39f.png)
（変数が参照しているアドレスは任意のアドレスを使いました。）

### intern()メソッド
恐らくJavaを勉強してもintern()メソッドを知らない方もいらっしゃるはずです。（実際に私もJava Silver資格の勉強をするとき知りました。）

Stringをリテラルで宣言する場合、内部処理でStringの`intern()`メソッドが呼び出されます。

`intern()`メソッドはStringをリテラルで宣言するとき、文字列がString Constant Poolに存在するかを確認します。存在するとそのPoolに保存されている文字列のアドレスを返し、なければ文字列をPoolに保存してそのアドレスを返します。

つまり、`intern()`メソッドを使うと`==`演算子を使って文字列の比較が可能となります。

```java
// リテラル 
String str1 = "kim";
// new()演算子
String str2 = new String("kim");

System.out.println(str1 == str2); // false
System.out.println(System.identityHashCode(str1)); // 2003749087
System.out.println(System.identityHashCode(str2)); // 1324119927

// intern()メソッドでPoolの中の"kim"のアドレスをreturn
str2 = str1.intern();

System.out.println(str1 == str2); // true
System.out.println(System.identityHashCode(str1)); // 2003749087
System.out.println(System.identityHashCode(str2)); // 2003749087
```
各str1とstr2変数はリテラルとnew()演算子で宣言されています。この場合、`==`演算子で比較するとfalseが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/cd2be956-86a4-fdc6-20dc-22298605d786.png)

次にstr2をstr1.intern()で再代入すると、str1の参照しているアドレスが代入され、`==`演算子で比較するとtrueが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678234/8f03e649-e0ae-8f6a-3e15-b464ae28ff10.png)

しかし、`intern()`メソッドにもデメリットはあります。

*   まず、Stringオブジェクトを作ります。
*   Stringのequals()でString Constant Poolに代入する文字列を探します。（時間がかかる）
*   String Constant Poolに保存されると、ガベージコレクションの処理対象から除外されます。（メモリ管理X）

これらを考えながら、コードを書くように気を付けましょう。

以上です。

# おわりに
２回目の記事でした。最後までご覧いただきありがとうございます。

ただの興味でいろいろ探して記事を書きましたが、かなりの長文になりました。
勉強すればするほど私のIT知識の足りなさを感じます。毎日少しずつ勉強するほかありませんね。。。
急がずにプログラミングの基礎から学んでいきたいと思います。あと、今より日本語が上手になりますように。。。

次回の記事はString、StringBuilder、StringBufferに関する記事になります。

ありがとうございます。


# 参考サイト
Oracle Java Silver 黒本
https://truehong.tistory.com/54
https://madplay.github.io/post/java-string-literal-vs-string-object
https://tech.pjin.jp/blog/2020/12/28/java_03_06_string/
https://readystory.tistory.com/139
https://shanepark.tistory.com/330