Edited at

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

More than 1 year has passed since last update.


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

Java で文字列str1とstr2が同じか確認する時にはstr1 == str2じゃないよ。

str1.equals(str2)ってやろうねってもうしょっちゅう言われてるし、耳たこレベルなんですけど。

「じゃあ、実際==String#equals(String)って何が違うねん」っていわれて説明できますかね。

よく使うメソッドなんで、知っといてもいいかなって。


String#equals(String) は何をしているのか。


本題の前に、一応おさらい。

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


よく見るサンプル

String str1 = new String("text");

String str2 = new String("text");

str1 == str2 // false になる!


上のコードであれば、書く人は普通に考えれば中身がtextで同じなので

trueが返ってくることを期待しているでしょう。

でも実際はfalseが返ってきますよ。と。

なぜなら、保存されている場所が違うから。

図の様に、str1str2が同じ場所にあるのものを取り出してくるのであれば、

この二つは同じものなので str1 == str2trueになるが…

図1.png

(図1)

内容が同じであっても違う場所にあるものを取り出してくるのであれば、

この二つは別物と判断されるので str1 == str2falseになると。

図2.png

(図2)

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


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

で、内容が同じなのに別物と判断されると困るので、

str1.equals(str2)とすると、純粋に中身で勝負してくれると。

では、具体的にString#equals(String)の中身を見てみましょう

(JDK1.8.xの場合)


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[](文字の配列)に文字列が配列の形で入れられています。

現状、thisstr1anObjectstr2

valueに文字一つずつが配列になったものが入っています。

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


1~3行目


1~3行目

if (this == anObject) {

return true;
}

シンプルに str1 == str2と同じ事ですね。

二つの変数が同じものを指しているのであれば、何ら問題無く二つは同じ内容であると。(図1の状態)

そうであればtrueを返して比較終了。

二つの変数が別の物を指している場合は処理を続けます。


4・19行目


4・19行目

if (anObject instanceof String) {

// 省略
}
return false;

実はString#equals()の引数はObjectなので何でも渡せるんですね。

で、String以外であれば比較が出来ないので、falseを返して終了。

とはいえ、これはjava.lang.Objectからのオーバーライドなのでこうなっているのであって、

何でも渡せることに深い意味はない…と思われます。

ちなみに、こちらの方は意味があることですが、

nullに対してinstanceof演算子を使うと必ずfalseが返ってくるので、

nullチェックにもなっています。

なので、引数にnullを渡してもエラーにならないのですね。

anObjectStringであれば次へ。


5~7行目


5~7行目

String anotherString = (String)anObject;

int n = value.length;
if (n == anotherString.value.length) {
// 省略
}

まず最初に、anObjectStringであることが確定したので、

改めてString型のanotherStringに入れ直し。

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

anotherStringに対しても同様に長さを取得し、同じ長さであればさらに比較作業を続ける。

長さが異なれば中身が違うことが確定するので、比較作業せずに下まで流してreturn false;ですな。

次。


8~16行目


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行目でそれぞれの文字の配列をv1v2に代入。

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ループの所は私だったら

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になってるのですね。