七色七音

「いつも」と「今日」は別の日だから……

[Java]Stringを==で比較すると意図せず意図した通りになる

JavaのStringを==で比較すんなってのは定番なので解説しませんが、ここではそれを説明するためのネットのサンプルソースがconcatで結合してる理由的なの。

ネットでは「意図しない挙動になる」と説明される事が多いですが、ここではむしろ逆に「意図した通りになる」パターンを

ああ、そうそう、実務でこのパターンが発生して困る事はそうそうないと思うので、これは暇つぶしのポエム的読み物としてどうぞ

最適化とStringと==

今から提示する例はJavaのバージョンで挙動が変わる可能性があります。参考までに今回の環境

java 9.0.1
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

最初にですが、以下のコードのifがfalseになる(メッセージが出力されない)事とその理由が分かるのがこの記事を読むうえで必要なレベルです。

String s1 = "aaabbb";
String s2 = "aaa".concat("bbb");
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

分からない人向けにざっと説明しておくと、==はインスタンスが同じかを比較する(Cで言うポインタの比較的な)ので、s1(aaabbb)とs2(aaaにbbbが結合された物)は「違うもの」になります

以下のコードがtrueになる理由が分かれば更に理解しやすいでしょう。

String s1 = "aaabbb";
String s2 = "aaabbb";
if (s1 == s2) {
    System.out.println("意図した通りになった");
}

こちらも説明しておくと、Stringはイミュータブル(不変)なので、Javaが「s1とs2は同じ物が使い回せるけんそうやってメモリ消費減らしとこーわい」と判断し同じインスタンスを使い回します。よって「同じもの」になります

それらを理解したうえで、試しに以下のコードを実行すれば、「意図せず意図した通り」になっているのが分かると思います。

String s1 = "aaabbb";
String s2 = "aaa" + "bbb";
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

+で結合している(別の物にならないといけない)のにifがtrueになる(同じ物として扱われている)のが分かると思います。

知っている人は知っていると思うのですが、これをデコンパイルすると以下のようなソースが得られます。

String s1 = "aaabbb";
String s2 = "aaabbb";
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

最適化された結果、「"aaa"+"bbb"はすなわち"aaabbb"なんじゃけんそうしとこ」からの「s1とs2って同じ物やけん使い回そーわい」が発動して「同じ物」となります

ちなみに、こっちはfalse

String s1 = "aaabbb";
String tmp = "aaa";
String s2 = tmp + "bbb";
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

これはjadでデコンパイルすると以下のように解釈されています。(jdだと元のソースが出てきます、jadだと最適化後のが得られる)

String s1 = "aaabbb";
String tmp = "aaa";
String s2 = (new StringBuilder()).append(tmp).append("bbb").toString();
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

finalを付けるとtrueになります。

String s1 = "aaabbb";
final String tmp = "aaa";
String s2 = tmp + "bbb";
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

jadでデコンパイルしたもの

String s1 = "aaabbb";
String tmp = "aaa";
String s2 = "aaabbb";
if (s1 == s2) {
    System.out.println("意図せず意図した通りになった");
}

生の文字列同士を+で結合するとひとまとめにされて、変数と結合すると新しいインスタンスが作成されます。(変数の中身がリフレクションとかで書き換えられるかも知れないので)

finalだと書き換えらえない事が約束されるので再び「すなわち"aaabbb"やけんそうしとこーわい」からの「使い回そーわい」が発動します。(リフレクションとかで存在確認をされる可能性があるので変数自体は残ります)

このように、Stringは状況次第で全く異なる解釈をされるので、==で比較すると「意図しない挙動」にもなるし「意図せず意図した通りの挙動」にもなります。危険なバグを孕む可能性があるのでequalで比較しましょうね。

と、いうお話でした。