Java で文字列の一致確認に使う equals() って実際どういう処理をしてるの？って話

# Java の文字列比較には String#equals(String) やね。

Java で文字列str1とstr2が同じか確認する時には`str1 == str2`じゃないよ。
`str1.equals(str2)`ってやろうねってもうしょっちゅう言われてるし、耳たこレベルなんですけど。

「じゃあ、実際`==`と`String#equals(String)`って何が違うねん」っていわれて説明できますかね。
よく使うメソッドなんで、知っといてもいいかなって。

#  String#equals(String) は何をしているのか。
## 本題の前に、一応おさらい。

`String#equals(String)`の中身を見る前に、なんで比較したらいけないのか、ざっくりおさらいを。

```java:よく見るサンプル
String str1 = new String("text");
String str2 = new String("text");

str1 == str2    // false になる！
```

上のコードであれば、書く人は普通に考えれば中身が`text`で同じなので
`true`が返ってくることを期待しているでしょう。

でも実際は`false`が返ってきますよ。と。
なぜなら、保存されている場所が違うから。

図の様に、`str1`と`str2`が同じ場所にあるのものを取り出してくるのであれば、
この二つは同じものなので `str1 == str2`が`true`になるが…

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/92484/1ef5981d-869c-32f4-9569-434c1648d820.png)
(図１)



内容が同じであっても違う場所にあるものを取り出してくるのであれば、
この二つは別物と判断されるので `str1 == str2`が`false`になると。

![図2.png](https://qiita-image-store.s3.amazonaws.com/0/92484/791e4989-4550-4c4c-7686-da3d22ab9bed.png)
（図２）

なので、`==`を使うと、内容が同じなのに`false`が返ってくることもあるわけですな。


## 本題の、String#equals(String)の中身

で、内容が同じなのに別物と判断されると困るので、
`str1.equals(str2)`とすると、純粋に中身で勝負してくれると。

では、具体的に`String#equals(String)`の中身を見てみましょう
（JDK1.8.xの場合）

```java:java.lang.String
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
```

ただし、フィールド変数として`char[]`（文字の配列）に文字列が配列の形で入れられています。
現状、`this`が`str1`、`anObject`が`str2`。
`value`に文字一つずつが配列になったものが入っています。


上から見ていきましょう。


### 1~3行目

```java:1~3行目
if (this == anObject) {
            return true;
}
```

シンプルに `str1 == str2`と同じ事ですね。
二つの変数が同じものを指しているのであれば、何ら問題無く二つは同じ内容であると。（図１の状態）
そうであれば`true`を返して比較終了。
二つの変数が別の物を指している場合は処理を続けます。


### 4・19行目

```java:４・１９行目
if (anObject instanceof String) {
    // 省略
}
return false;
```

実は`String#equals()`の引数はObjectなので何でも渡せるんですね。
で、`String`以外であれば比較が出来ないので、`false`を返して終了。

とはいえ、これは`java.lang.Object`からのオーバーライドなのでこうなっているのであって、
何でも渡せることに深い意味はない…と思われます。

ちなみに、こちらの方は意味があることですが、
`null`に対して`instanceof`演算子を使うと必ず`false`が返ってくるので、
`null`チェックにもなっています。
なので、引数に`null`を渡してもエラーにならないのですね。

`anObject`が`String`であれば次へ。


### 5~7行目

```java:5~7行目
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
    // 省略
}
```

まず最初に、`anObject`が`String`であることが確定したので、
改めて`String`型の`anotherString`に入れ直し。

次に、`value`、すなわち配列に対して`length`をかけて、文字の配列の長さを変数`n`に代入

`anotherString`に対しても同様に長さを取得し、同じ長さであればさらに比較作業を続ける。
長さが異なれば中身が違うことが確定するので、比較作業せずに下まで流して`return false;`ですな。

次。

### 8~16行目

```java:8~16行目
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
    if (v1[i] != v2[i])
        return false;
        i++;
    }
return true;
```

最後はまとめていきましょう。

8・9行目でそれぞれの文字の配列を`v1`、`v2`に代入。

10行目でカウンタとして使用する変数`i`を定義。

11行目の`while`は6行目で取得していた配列の長さ`n`を一つずつ引いていき、0になるまで（配列の数だけ）中身を繰り返し。
ちなみに後置デクリメントなので、 `n != 0`を確認してから`n = n - 1`ですね。よって`n`はマイナスにはならないと。

12~14行目でお待ちかねの文字比較ですね。
それぞれの配列から順に一つずつ取り出した文字が同じであることを確認していきます。
途中で違う箇所があればそこで比較終了。`return false;`で、
全部一致（つまり、違う箇所が一つもなく、`v1[i] != v2[i]`に一度も引っかからず`n`が0に到達した）であれば
晴れて`return true;`となります。

# わりと地道に比較してた

どうやら`String#equals(String)`では一つずつ文字をつきあわせて比較してくれていたようですね。
良かった。

ただ、一カ所疑問があって、`while`ループの所は私だったら

```java
for (int i = 0; i < n; i++) {
    if (v1[i] != v2[i])
        return false;
}

/** 公式はこう
while (n-- != 0) {
    if (v1[i] != v2[i])
        return false;
        i++;
    }
*/
```

こんな感じで`for`で書くと思うんですよな。
`n--`てちょっとあんまり見ない気がする。
`while`ループには何か理由あるんですかね？

もし理由が分かれば追記しますね。


# 追記(2016/10/10)

`while`ループの疑問について、コメントいただきました。

ぜひ、コメント欄も参照下さい。
きちんと意味があって`while`になってるのですね。