今回は文字列の比較について、なぜequalsメソッドで比較するのかも踏まえて解説してみたいと思います。
“equals”と”==”
何はともあれ、まずはこの二つを使って比較してみましょう。
まずはequalsメソッドから。
public class Main { public static void main(String[] args) { // 変数に代入するのではなく、文字列を生成する String foo = new String("abc"); String bar = new String("abc"); Boolean result = foo.equals(bar) ? true : false; System.out.println(result); } }
実行結果
true
上のコードは、三項演算子を使い呼び出し元の文字列の値と同一かどうかについての判定をBoolean型で出力させるだけの単純なコードです。
equalsメソッドは引数に比較対象となる変数を指定し、呼び出し元の文字列の値が同一かどうかで比較します。
今回の実験では変数fooに生成された文字列と変数barに生成された文字列が同一という判定が出ましたので、実行結果ではtrueとなっていることが分かります。
では次に”==”を使って同じようなコードで実験してみましょう。
public class Main { public static void main(String[] args) { // 変数に代入するのではなく、文字列を生成する String foo = new String("abc"); String bar = new String("abc"); Boolean result = (foo == bar) ? true : false; System.out.println(result); } }
実行結果
false
コード内容はequalsと一緒ですが、一部”foo.equals(bar)”の部分を”(foo == bar)”としています。
“equals”と”==”で同じ動作が期待できるのであれば、返ってくる値も同じであるはずなのに、今回はfalseとなってしまいました。
この結果に混乱している方も多いと思いますので、次の項目では「Javaの参照について」の解説をしていきます。
Javaの参照について
Javaのデータ型にはプリミティブ型と参照型という二つの型が存在します。
プリミティブ型とは次の8種類の型を指します。
左から「型名 – 分類 – サイズ(bit)」となっています。
- long – 整数 – 64
- int – 整数 – 32
- short – 整数 – 16
- byte – 整数 – 8
- double – 少数 – 64
- float – 少数 – 32
- boolean – 真偽 – 無し
- char – 文字 – 16
これ以外の型は全て参照型となります。
プリミティブ型の特徴としては、それ自体がメソッドを持っておらず、メモリに書き込まれるデータ値を代入することが出来る点です。
これに対し参照型は、メモリにデータ値ではなくnew演算子で生成したオブジェクトのポインタ(アドレス)を参照値として代入しています。
この違いを分かりやすく説明すると、参照型の場合はメモリ内部の動作として、まず格納されるデータのアドレスを決定し、その次に決定されたアドレスへデータを格納しています。
このため、オブジェクトを生成した瞬間にメモリ上では一つの領域が確保され、その領域の中にデータを格納します。
ですから次に参照する場合には、メモリは参照されたデータがどの領域に格納されているかを探し出すため、アドレスが違うとデータが同じでも”別物”として扱ってしまうわけです。
“==”での比較は”equals”のように予め決められた動作ではなく漠然とした解釈での比較となる為、このような違いが出てきます。
では次のコードではどうでしょう?
public class Main { public static void main(String[] args) { // 変数に代入するのではなく、文字列を生成する String foo = "abc"; String bar = "abc"; Boolean result = (foo == bar) ? true : false; System.out.println(result); } }
実行結果
true
今度は”true”となってしまいました。先ほどした説明だと矛盾してると思ってる方も多いはずです。
しかし実は全く矛盾していません。この理由として今回のコードはnew演算子を使っておらず、参照型のStringにプリミティブ型のcharを代入しているだけだからです。
少し例えを変えてご説明しましょう。
“abc”さん夫妻が家を借りる時に別々に不動産屋さんに行った場合、同じ姓でも別世帯だと思われますよね?当然不動産屋さんは別々の部屋を用意します。まずこれが参照型の場合のメモリの動きです。
では”abc”さん夫妻が一緒に不動産屋さんに家探しに行った場合、別々の部屋を用意するでしょうか?同じ家族ということが分かっているので通常は一つの家を用意します。これがプリミティブ型の場合のメモリの動きとなります。
最後に記述したコード(new演算子を使っていない場合)では、メモリはまずcharというプリミティブ型の文字列を記憶し、そのデータ値をそれぞれの変数に代入しているためこのような結果となったわけです。
このことから、正しく文字列の比較を行うためにはequalsメソッドを使うのが正しいということがお分かりいただけたと思います。
equalsの注意点
しかしequalsメソッドを使う場合にも注意は必要となります。
まずは次のコードを見てみましょう。
public class Main { public static void main(String[] args) { // 変数に代入するのではなく、文字列を生成する String foo = null; String bar = "abc"; Boolean result = foo.equals(bar) ? true : false; System.out.println(result); } }
実行結果
Exception in thread "main" java.lang.NullPointerException at SampleInteger.Main.main(Main.java:12)
このように、比較する変数のどちらかが”null”となっていた場合、NullPointerExceptionが発生します。
これを回避するためには、”==”を使い分岐文で予めnullチェックをするかObjects.equalsメソッドを使う必要がありますが、Objects.equalsメソッドを使う場合、比較する変数の両方がnullだった場合もtrueとなるため、状況によっては”==”を使って予めnullチェックをする方が賢明かもしれません。
まとめ
いかがでしたか?今回は文字列の比較について二つの方法の解説をしてみました。
違いが分かっていなかった方もこの記事を読んで理解していただけると嬉しく思います。
プリミティブ型と参照型のメモリ動作の違いについても、プログラミングの経験があまりない方はしっかりとした理解が出来ていない場合も多く見られますので、この機会にしっかりと理解を深めていきましょう。