Code Fix

中級

== と .equals() の違い(文字列比較)

Java初学者が最も多く踏む罠。参照比較と値比較の違い、定数プール、null安全な書き方まで扱います。

なぜエラーが出ないのか

出力: false
(エラーなし)
javacは何も報告しません。文法として正しいためです
出力: false
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  public class Main {
 2      public static void main(String[] args) {
 3          String a = new String("java");
 4          String b = new String("java");
 5          System.out.println(a == b      );
                                     ^
 6      }
 7  }
出力: false期待: true (コンパイルは通っています)

== は参照が同じかを見ます。newで作った2つのStringは中身が同じでも別のオブジェクトです。

直し方: == b を .equals(b) にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  import java.util.Scanner;
 2  
 3  public class Main {
 4      public static void main(String[] args) {
 5          Scanner sc = new Scanner(System.in);
 6          String cmd = sc.nextLine();
 7          if (cmd == "quit"     ) {
                          ^
 8              System.out.println("quit");
 9          }
10      }
11  }
出力: (何も出ない)期待: quit (コンパイルは通っています)

入力から作られたStringは定数プールを使いません。== では絶対に一致しない典型パターンです。

直し方: == "quit" を .equals("quit") にします。

この問題を解いてみる →

パターン3

 1  public class Main {
 2      public static void main(String[] args) {
 3          String s = null;
 4          System.out.println(s.equals("a")   );
                                       ^
 5      }
 6  }
Exception in thread "main" java.lang.NullPointerExceptionexited with code 1

nullに対してメソッドは呼べません。定数を左に置くとnull安全に比較できます。

直し方: s.equals("a") を "a".equals(s) にします。

この問題を解いてみる →

パターン4

 1  public class Main {
 2      public static void main(String[] args) {
 3          String a = "Java";
 4          String b = "java";
 5          System.out.println(a.equals(b)              );
                                            ^
 6      }
 7  }
出力: false期待: true (コンパイルは通っています)

equalsは大文字小文字を区別します。無視して比較するにはequalsIgnoreCaseを使います。

直し方: .equals(b) を .equalsIgnoreCase(b) にします。

この問題を解いてみる →

パターン5

 1  public class Main {
 2      public static void main(String[] args) {
 3          StringBuilder sb = new StringBuilder("java");
 4          String s = "java";
 5          System.out.println(s.equals(sb)       );
                                         ^
 6      }
 7  }
出力: false期待: true (コンパイルは通っています)

equalsは相手がStringでなければ常にfalseです。内容だけを比較するにはcontentEqualsを使います。

直し方: .equals(sb) を .contentEquals(sb) にします。

この問題を解いてみる →

よくある誤解

== でたまたま動くことがあります。リテラル同士は定数プールで同じ実体になるためです。「動いたから正しい」が最も危険なパターンです。

実務での勘所

==の罠を完全になくすことはできませんが、被害を減らす手立てはあります。比較の左側をリテラルにすると(s.equals("abc")ではなく"abc".equals(s))、sがnullでもNPEにならず安全です。両方がnullになり得るなら、Objects.equals(a, b)を使えばnullチェックごと済みます。SpotBugsやIntelliJ IDEAの標準インスペクションは、文字列同士の==比較を警告として検出します。そもそもJavaが==を演算子オーバーロードで再定義できないようにしているのは、==の意味を型ごとに変えられると可読性が壊れるという設計判断のためです。演算子の意味を型ごとに変えられるC++とは、この点で事情が異なります。

演習をはじめる

関連するエラー

他の言語ではどうなるか

広告
広告スロット(未設定)