Javaの文字列比較を完全解説|==・equals・compareToの違いと使い分け

目次

1. はじめに

Javaで文字列を比較する重要性とは?

Javaのプログラミングにおいて、文字列(String)を扱う場面は非常に多くあります。ユーザー名のチェック、フォームからの入力値の照合、APIレスポンスの確認など、あらゆる場面で文字列の比較が求められます。

このとき、「文字列をどうやって正しく比較するか」は、意外と初学者がつまずきやすいポイントです。特に、==演算子とequals()メソッドの違いを理解していないと、意図しない結果を引き起こすバグの原因になります。

「==」と「equals」の違いがわからないと危険

例えば、次のようなコードを見てください。

String a = "apple";
String b = new String("apple");

System.out.println(a == b);       // 結果: false
System.out.println(a.equals(b));  // 結果: true

このコードの出力結果を見て驚く方も多いでしょう。同じ文字列なのに、==ではfalse、equals()ではtrueとなるのです。これはJavaが「文字列を参照型として扱っている」ためで、==は参照アドレスを比較しているのです。

このように、文字列の比較を正しく行うことは、プログラムの信頼性や可読性に直結します。逆に言えば、正しい方法を理解していれば、バグを未然に防ぐことができます。

本記事で学べること

本記事では、Javaにおける文字列の比較方法について、基本から応用までを丁寧に解説していきます。以下のような疑問に答えながら、初心者にも分かりやすく構造的に説明していきます。

  • ==equals()の違いは?
  • 大文字・小文字を無視して比較するには?
  • 文字列を辞書順で比較するには?
  • nullとの比較で例外を避けるには?

実務でも役立つコード例を交えながら、正しい文字列比較の知識をしっかり身につけていきましょう。

2. Javaにおける文字列の基本

文字列は「参照型」である

Javaにおいて、String型はプリミティブ型(intやbooleanなど)ではなく、「参照型(Reference Type)」です。これは、String変数が実際の文字列データそのものではなく、ヒープメモリ上に存在する文字列オブジェクトを参照しているということを意味します。

つまり、次のように書いた場合:

String a = "hello";
String b = "hello";

abは同じ文字列 "hello" を参照しているため、a == btrue を返す可能性があります。ただし、これはJavaの文字列リテラルの最適化(String Interning)という仕組みによるものです。

文字列リテラルと new String() の違い

Javaでは、同じ文字列リテラルを複数回使った場合、それらは同じ参照として最適化されます。これは、Javaが実行時に文字列を共有してメモリ効率を上げるための機能です。

String s1 = "apple";
String s2 = "apple";
System.out.println(s1 == s2); // true(同じリテラルなので同一参照)

一方で、newキーワードを使って明示的にオブジェクトを生成すると、新しい参照が作られます。

String s3 = new String("apple");
System.out.println(s1 == s3); // false(異なる参照)
System.out.println(s1.equals(s3)); // true(内容は同じ)

このように、==参照先の一致を見るのに対して、equals()内容の一致を確認するため、用途が大きく異なります。

Stringは「不変(イミュータブル)」なクラス

もう一つの重要な特徴は、Stringイミュータブル(immutable)であることです。つまり、一度作成したStringオブジェクトの内容は変更できません。

例えば次のように記述すると:

String original = "hello";
original = original + " world";

これは元のoriginalに文字列を加えているように見えますが、実際は新しいStringオブジェクトが生成され、それがoriginalに代入されているのです。

この不変性のおかげで、Stringはスレッドセーフであり、セキュリティやキャッシュ最適化にも役立ちます。

3. 文字列比較の方法

== 演算子による参照比較

==は、文字列オブジェクトの参照(アドレス)を比較します。つまり、同じ内容であっても異なるオブジェクトであれば false を返します。

String a = "Java";
String b = new String("Java");

System.out.println(a == b);        // false

この例では a はリテラル、bnew によって生成されており、参照が異なるため false となります。内容の比較には使えないので注意が必要です。

equals() メソッドによる内容比較

equals() は、文字列の内容を比較する正しい方法です。多くの場面でこのメソッドを使うことが推奨されます。

String a = "Java";
String b = new String("Java");

System.out.println(a.equals(b));   // true

このように、参照が異なっていても、内容が同じであれば true を返します。

nullとの比較における注意点

次のようなコードは、NullPointerException を引き起こす可能性があります。

String input = null;
System.out.println(input.equals("test")); // 例外発生!

これを避けるためには、定数.equals(変数) の形で書くことが推奨されます。

System.out.println("test".equals(input)); // false(安全)

equalsIgnoreCase() メソッドによる大文字・小文字を無視した比較

ユーザー名やメールアドレスなど、大文字小文字を区別しない比較をしたい場合は equalsIgnoreCase() が便利です。

String a = "Hello";
String b = "hello";

System.out.println(a.equalsIgnoreCase(b)); // true

ただし、Unicodeにおける一部の特殊なケース(トルコ語の”İ”など)では想定外の動作をすることもあるため、国際化対応では追加の配慮が必要です。

compareTo() メソッドによる辞書順比較

compareTo() は、2つの文字列を辞書順で比較し、以下のような整数を返します。

  • 0:等しい
  • 負の値:呼び出し元の文字列が前(小さい)
  • 正の値:呼び出し元の文字列が後(大きい)
String a = "apple";
String b = "banana";

System.out.println(a.compareTo(b)); // 負の値("apple"は"banana"より前)

辞書順ソートやフィルタ処理に活用されることが多く、Collections.sort()TreeMap のキー比較でも内部的に使われます。

4. 実践的な使用例

ユーザー入力の照合(ログイン機能)

もっともよく使われる場面の一つが、ユーザー名やパスワードの一致判定です。

String inputUsername = "Naohiro";
String registeredUsername = "naohiro";

if (registeredUsername.equalsIgnoreCase(inputUsername)) {
    System.out.println("ログイン成功");
} else {
    System.out.println("ユーザー名が一致しません");
}

この例のように、大文字・小文字を無視して比較したい場面では equalsIgnoreCase() を使うのが適切です。

ただし、セキュリティ的にパスワード比較では大小文字を区別すべきなので、equals() を使いましょう。

入力バリデーション(フォーム処理)

例えば、ドロップダウンやテキストボックスからの入力値のチェックにも、文字列比較が使われます。

String selectedOption = request.getParameter("plan");

if ("premium".equals(selectedOption)) {
    System.out.println("プレミアムプランを選択しました。");
} else {
    System.out.println("その他のプランです。");
}

このように、nullチェックを兼ねた安全な比較として "定数".equals(変数) の形式が実務でよく使われます。ユーザー入力は必ずしも値が存在するとは限らないため、NullPointerExceptionを防ぐための書き方です。

複数条件の分岐処理(スイッチ的に使う)

複数の文字列候補を条件分岐で扱いたい場合は、equals()を連続して使うのが一般的です。

String cmd = args[0];

if ("start".equals(cmd)) {
    startApp();
} else if ("stop".equals(cmd)) {
    stopApp();
} else {
    System.out.println("コマンドが不正です");
}

Java 14以降では、文字列に対する switch 文も正式に使えるようになっています。

switch (cmd) {
    case "start":
        startApp();
        break;
    case "stop":
        stopApp();
        break;
    default:
        System.out.println("不明なコマンドです");
}

このように、文字列比較はロジックの分岐処理に直結するため、正確な理解が求められます。

nullとの比較で起こるバグとその対策

よくある失敗例として、null値との比較でアプリがクラッシュするケースがあります。

String keyword = null;

if (keyword.equals("検索")) {
    // 例外発生:java.lang.NullPointerException
}

このようなケースでは、次のように書くことで安全に比較できます。

if ("検索".equals(keyword)) {
    System.out.println("検索実行");
}

または、より厳密なnullチェックを先に行う方法もあります。

if (keyword != null && keyword.equals("検索")) {
    System.out.println("検索実行");
}

null安全なコードは堅牢性を高めるうえで必須のスキルです。

5. パフォーマンスと最適化

文字列比較における処理コスト

equals()compareTo() は、一般に高速に動作するよう最適化されていますが、内部では1文字ずつ比較しているため、長い文字列や大量データを扱うときには影響が出ます。特に、ループの中で何度も同じ文字列と比較していると、意図しないパフォーマンス低下につながることがあります。

for (String item : items) {
    if (item.equals("keyword")) {
        // 比較回数が多い場合、注意
    }
}

String.intern() による比較高速化

JavaのString.intern()メソッドを使うと、同じ内容の文字列をJVMの「文字列プール」に登録し、参照を共有することができます。これを利用すると、==での比較も可能になるため、性能上のメリットがある場合があります。

String a = new String("hello").intern();
String b = "hello";

System.out.println(a == b); // true

ただし、文字列プールを乱用するとヒープ領域が圧迫される可能性があるため、限られた用途での使用に留めるべきです。

equalsIgnoreCase() の落とし穴と代替案

equalsIgnoreCase() は便利ですが、比較時に大文字・小文字を変換する処理が発生するため、通常のequals()よりも若干コストが高くなることがあります。性能がシビアな場面では、すでに大文字や小文字に統一した値で比較する方が高速です。

String input = userInput.toLowerCase();
if ("admin".equals(input)) {
    // 高速化された比較
}

このように事前に変換してからequals()を使うと、比較処理の効率を高められます。

StringBuilder / StringBuffer の活用

大量の文字列連結が発生するようなケースでは、String を使っていると毎回新しいオブジェクトが生成され、メモリやCPUへの負荷が高くなります。比較処理が混在する場合も含め、連結や構築には StringBuilder を使い、比較には String のまま保持するのがベストプラクティスです。

StringBuilder sb = new StringBuilder();
sb.append("user_");
sb.append("123");

String result = sb.toString();

if (result.equals("user_123")) {
    // 比較処理
}

キャッシュと事前処理で高速化する設計

同じ文字列との比較が何度も行われるような場合は、一度比較結果をキャッシュする、あるいはマップ(HashMapなど)を使って前処理することで、比較処理自体を減らすという手法も有効です。

Map<String, Runnable> commandMap = new HashMap<>();
commandMap.put("start", () -> startApp());
commandMap.put("stop", () -> stopApp());

Runnable action = commandMap.get(inputCommand);
if (action != null) {
    action.run();
}

このようにすれば、equals()による文字列比較は1回のMap検索に置き換わり、可読性とパフォーマンスの両方を向上させることができます。

6. よくある質問(FAQ)

Q1. ==equals()の違いは何ですか?

A.
==参照の比較(つまりメモリ上のアドレスの一致)を行います。一方でequals()は、文字列の中身(内容)を比較します。

String a = new String("abc");
String b = "abc";

System.out.println(a == b);        // false(参照が異なる)
System.out.println(a.equals(b));   // true(内容は同じ)

したがって、文字列の内容を比較したいときは必ずequals()を使用しましょう。

Q2. equals()を使うとき、nullが原因でエラーになるのはなぜ?

A.
nullに対してメソッドを呼び出すと、NullPointerExceptionが発生するためです。

String input = null;
System.out.println(input.equals("test")); // 例外発生!

このエラーを防ぐには、次のように定数側から比較する書き方が安全です。

System.out.println("test".equals(input)); // false(安全)

Q3. 大文字・小文字を無視して比較するにはどうすればいい?

A.
equalsIgnoreCase() メソッドを使うと、大文字と小文字の違いを無視して比較できます。

String a = "Hello";
String b = "hello";

System.out.println(a.equalsIgnoreCase(b)); // true

ただし、全角文字や一部の特殊なUnicode文字では、結果が予期しないものになることもあるため、注意が必要です。

Q4. 文字列を並び順で比較したい場合は?

A.
文字列の辞書順の前後関係を調べたいときには、compareTo()を使います。

String a = "apple";
String b = "banana";

System.out.println(a.compareTo(b)); // 負の値("apple"は"banana"より前)

戻り値の意味:

  • 0 → 等しい
  • 負の値 → 左側が前
  • 正の値 → 左側が後

ソート処理などで活用されます。

Q5. 文字列比較で覚えておくべきベストプラクティスは?

A.

  • 内容の比較には必ず equals() を使う
  • null安全性を意識して "定数".equals(変数) の形を心がける
  • 大小文字の無視には equalsIgnoreCase() または事前の toLowerCase() / toUpperCase() 処理
  • 大量比較や高速化が必要な場面では、intern()やキャッシュ設計も検討
  • 可読性と安全性のバランスを常に意識すること

7. まとめ

Javaの文字列比較は「正しく使い分ける」ことが重要

この記事を通じて、Javaにおける文字列比較の基本から実践、パフォーマンス面までを総合的に解説してきました。Javaでは、Stringが参照型であることから、比較の方法を誤ると意図しない動作になってしまうケースが少なくありません。

特に==equals()の違いは、初心者が最初に混乱しやすいポイントです。これを正しく理解し、使い分けることが、安全で信頼性の高いコードを書くうえで欠かせません。

この記事で学んだこと(チェックリスト)

  • == は参照(メモリアドレス)を比較する演算子
  • equals() は文字列の内容を比較するメソッド(最も安全)
  • equalsIgnoreCase() を使えば大文字小文字を無視できる
  • compareTo() で文字列の辞書順比較が可能
  • "定数".equals(変数) の順番で null 安全に比較できる
  • パフォーマンスを意識する場合は intern() やキャッシュ設計も有効

実務でも活きる、文字列比較の知識

ログイン判定や入力バリデーション、データベース検索や条件分岐など、文字列の比較は日常的な開発業務に密接に関わる要素です。この知識があることで、バグを防ぎ、意図通りに動作するコードを実現できるようになります。

今後、文字列を扱う処理を書く際には、ぜひ本記事の内容を参考に、目的に合った比較方法を選択してください。