Javaの文字列比較を徹底解説|==とequalsの違い・安全な比較方法
CONTENTS
Java学習者が最初に直面し、かつ実務でもバグの原因となりやすいテーマの一つが「文字列比較」です。
一見するとシンプルに見える比較処理ですが、== 演算子と equals() メソッドの決定的な違いを理解していないと、重大な不具合を引き起こします。
例えば、開発環境では動いていたコードが、本番環境で特定の入力値になった途端に動かなくなる、といったケースも珍しくありません。
また、近年のJava開発現場では、単に比較するだけでなく、大文字・小文字の区別や、NullPointerException(NPE)を防ぐ「安全な書き方」が求められます。
本記事では、初心者が必ずつまずく比較の仕組みを基礎から体系的に解説します。
文字列比較の基本|なぜ == で比較してはいけないのか
多くのJava初心者は、数値の比較と同じ感覚で、次のようなコードを書いてしまいがちです。
// 初心者がやりがちな間違い
String str = getTitle(); // 外部から取得した文字列
if (str == "Java入門") {
System.out.println("一致しました");
}
しかし、この比較は文字列の中身(文字の並び)を比較していません。
Javaにおける == 演算子は、オブジェクトの「参照(メモリアドレス)の一致」を確認するためのものです。
つまり、「メモリ上の同じ場所に保存されているデータかどうか」を判定しているに過ぎません。
したがって、たとえ文字列の中身が完全に同じ「Java入門」であったとしても、メモリ上の保存場所が異なれば、結果は false になってしまいます。
Java公式チュートリアルでも、文字列比較には必ずメソッドを使用するよう明記されています。
== と equals の仕組みの違いを深掘りする
Javaでは、比較対象によって明確な使い分けが必要です。以下の違いを完全に暗記してください。
- == 演算子:参照の比較(同一のオブジェクトインスタンスか)
- equals():内容の比較(文字列の並びが同じか)
この違いを理解するために、少し極端な例を見てみましょう。
String a = new String("hello");
String b = new String("hello");
// 参照(アドレス)の比較
System.out.println(a == b); // false
// 内容(文字)の比較
System.out.println(a.equals(b)); // true
new String() を使用して生成された文字列は、ヒープメモリ上の異なる場所に強制的に作られます。
そのため、中身が同じ「hello」であっても、== で比較すると「別の物体」とみなされ false が返されます。
一方で、equals() メソッドは、参照先にある文字列データを1文字ずつチェックしてくれるため、意図通りの true が返ってきます。
なぜ時々 == でも一致してしまうのか?(String Poolの罠)
ここが最も初心者を混乱させるポイントです。
「equals を使うべきと習ったけれど、== でも動いたことがある」という経験はありませんか?
それは、Javaのメモリ最適化機能である「String Constant Pool(文字列定数プール)」が働いているためです。
String a = "test";
String b = "test";
System.out.println(a == b); // true(なぜ?)
Javaでは、ダブルクォート " " で囲ったリテラルとして文字列を作成した場合、Java VMはその文字列をプール(専用の保管領域)に保存し、再利用しようとします。
上記のコードでは、変数 a で作成した "test" がプールにあるため、変数 b は新たにオブジェクトを作らず、既存の "test" を参照します。
その結果、参照先が同じになり、たまたま == が true になるのです。
しかし、実務において文字列は、ユーザー入力やデータベース、ファイル読み込みなど「外部」からやってきます。
外部から動的に生成された文字列はプールを経由しないことが多く、== での判定は失敗します。
この「動く時と動かない時がある」という不安定さを排除するためにも、どのような生成方法であっても必ず equals を使うというルールが鉄則なのです。
実務での正しい比較パターン3選
ここからは、実際の開発現場で使われている「安全で正しい比較コード」を紹介します。
状況に応じて、以下の3つのパターンを使い分けてください。
1. 大文字小文字を無視する equalsIgnoreCase
ユーザーが入力したメールアドレスやログインIDを比較する場合、「User」と「user」を同じものとして扱いたいケースがあります。
その際に使用するのが equalsIgnoreCase です。
String inputId = "Admin";
if ("admin".equalsIgnoreCase(inputId)) {
System.out.println("ログイン成功");
}
一方で、パスワードやハッシュ値、厳密なファイル名のチェックなどでは、必ず通常の equals を使用して完全一致を確認してください。
2. NullPointerExceptionを防ぐ「ヨーダ記法」
実務で頻発するエラーの代表格が NullPointerException(ぬるぽ)です。
変数が null の状態でメソッドを呼び出すと、プログラムはクラッシュします。
String str = null;
// これはエラー(NPE)になります
if (str.equals("OK")) { ... }
これを防ぐための伝統的なテクニックが、定数(リテラル)を左側に書く方法です。
// これなら安全(falseになるだけ)
if ("OK".equals(str)) { ... }
"OK" は確実にオブジェクトとして存在しているため、そこから equals を呼ぶ分にはエラーになりません。
見た目が少し奇妙に見えるため「ヨーダ記法」とも呼ばれますが、Java開発における防衛的プログラミングの基本です。
3. 最もモダンで安全な Objects.equals
Java 7以降、最も推奨される比較方法は java.util.Objects クラスを使用することです。
このユーティリティメソッドを使えば、両方の値が null であっても安全に判定を行ってくれます。
import java.util.Objects;
String str1 = null;
String str2 = "test";
// nullチェックを内部で自動で行ってくれる
if (Objects.equals(str1, str2)) {
// 一致時の処理
}
可読性が高く、どちらが null かを気にする必要がないため、近年のコーディング規約ではこの方法が標準になりつつあります。
詳しい仕様はJava公式ドキュメントのObjects.equalsで確認できます。
まとめ
Javaの文字列比較は、一見単純なようでいて、メモリ管理やNull安全性といった重要な概念を含んでいます。
本記事の重要ポイントをまとめます。
==は参照比較:別インスタンスだと内容は同じでもfalseになるequalsは内容比較:文字列の中身を確認するため、通常はこちらを使う- String Poolの存在:リテラル生成時はプールされるため
==が通ることもあるが、依存してはいけない - Null安全対策:
"文字列".equals(変数)またはObjects.equals(変数, 変数)を活用する
これらの仕様を理解せずにコーディングを行うと、本番環境で予期せぬ不具合を招く原因になります。
もし、「String Poolの仕組みがイメージしにくい」「実務レベルの例外処理にまだ不安がある」と感じる場合は、コードレビューを通じて現役エンジニアから正しい作法を学ぶことが、スキルアップへの近道です。
つまずいたら、現役エンジニアと一緒に解決しよう
Javaの文字列比較やequalsの理解は、独学ではつまずきやすい部分です。さらに理解を深めたい方は、Zerocode Onlineで実務直結のJavaを体系的に学んでみませんか。
比較・null処理の疑問を丁寧にサポート。
Java/SQL/Spring Bootを体系的に理解。
実務で評価されるコードを書けるようになる。
忙しくても学べる環境が整っています。