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
が返ってきますよ。と。
なぜなら、保存されている場所が違うから。
図の様に、str1
とstr2
が同じ場所にあるのものを取り出してくるのであれば、
この二つは同じものなので str1 == str2
がtrue
になるが…
内容が同じであっても違う場所にあるものを取り出してくるのであれば、
この二つは別物と判断されるので str1 == str2
がfalse
になると。
なので、==
を使うと、内容が同じなのにfalse
が返ってくることもあるわけですな。
本題の、String#equals(String)の中身
で、内容が同じなのに別物と判断されると困るので、
str1.equals(str2)
とすると、純粋に中身で勝負してくれると。
では、具体的にString#equals(String)
の中身を見てみましょう
(JDK1.8.xの場合)
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行目
if (this == anObject) {
return true;
}
シンプルに str1 == str2
と同じ事ですね。
二つの変数が同じものを指しているのであれば、何ら問題無く二つは同じ内容であると。(図1の状態)
そうであればtrue
を返して比較終了。
二つの変数が別の物を指している場合は処理を続けます。
4・19行目
if (anObject instanceof String) {
// 省略
}
return false;
実はString#equals()
の引数はObjectなので何でも渡せるんですね。
で、String
以外であれば比較が出来ないので、false
を返して終了。
とはいえ、これはjava.lang.Object
からのオーバーライドなのでこうなっているのであって、
何でも渡せることに深い意味はない…と思われます。
ちなみに、こちらの方は意味があることですが、
null
に対してinstanceof
演算子を使うと必ずfalse
が返ってくるので、
null
チェックにもなっています。
なので、引数にnull
を渡してもエラーにならないのですね。
anObject
がString
であれば次へ。
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行目
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
ループの所は私だったら
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
になってるのですね。