【Java】String と StringBuilder の == 演算子と equals メソッドの違い

概要

仕事で Java を扱うようになったので、年末年始の休みを利用して Java SE8 Silver の試験を受けてみました。

他の言語から来た自分にとって Java の String クラス(文字列)は少し扱いづらく、混乱したのを覚えています。

試験には無事合格したのですが、フィードバックに== 演算子と equals メソッドを使用した場合の文字列と他のオブジェクトの比較が分かってない的な事が書いてあり、「やっぱりな ... 」と思いました。

他のオブジェクトと言うのは、おそらく StringBuilder クラスのオブジェクトのことで、String クラスと StringBuilder クラスが混ざると、それぞれの文字列の比較が曖昧になっていました。

試験前は時間がなくて、まとめている余裕がなかったのですが、試験が終わって一段落したので、分からなかった部分をまとめたいと思います。

 

はじめに

== 演算子と equals メソッドの違いは同一性を判定するか、同値性を判定するかによるものです。

基本的に == 演算子は同一性を判定し、equals メソッドは同値性を判定します。

同一性とは変数が同じインスタンスを参照していることを言います。

一方、同値性とは変数が同じ値を持っていることを言います。

 

String クラスの場合

String クラスの == 演算子は 同一性 を判定します。

String クラスの equals メソッドは 同値性 を判定します。 

public class Sample {
    public static void main(String args[]) {
        String str1 = "abc";
        String str2 = "abc";

        str1 += "def";
        str2 += "def";

        System.out.println("str1: \"" + str1 + "\", str2: \"" + str2 + "\"");
        System.out.println("== 演算子の結果: " + (str1 == str2));
        System.out.println("equals メソッドの結果: " + str1.equals(str2));
    }
}

str1 と str2 の変数は値は同じですが、参照先が違うので以下の結果となります。

str1: "abcdef", str2: "abcdef"
== 演算子の結果: false
equals メソッドの結果: true

 

SringBuilder クラスの場合

StringBuilder クラスの == 演算子も 同一性 を判定します。

しかし、StringBuilder クラスの equals メソッドは 同値性 ではなく 同一性 を判定します。

これは StringBuilder クラスで equals メソッドがオーバーライドされていないため、親クラスである Object クラスの equals メソッドをそのまま呼び出してしまうからです。

public class Sample {
    public static void main(String args[]) {
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();

        sb1.append("abc");
        sb1.append("def");

        sb2.append("abc");
        sb2.append("def");

        System.out.println("sb1: \"" + sb1 + "\", sb2: \"" + sb2 + "\"");
        System.out.println("== 演算子の結果: " + (sb1 == sb2));
        System.out.println("equals メソッドの結果: " + sb1.equals(sb2));
    }
}

sb1 と sb2 の変数は値が同じです。

ただし、StringBuilder クラスでは == 演算子も equals メソッドも同一性を確認するので両方 false となります。

sb1: "abcdef", sb2: "abcdef"
== 演算子の結果: false
equals メソッドの結果: false

 

String クラスと StringBuilder クラスを混ぜた場合

String クラスの文字列と StringBuilder クラスの文字列を比較したい時にはどちらかに統一しなくてはいけません。

無理矢理比較しようとすると以下のエラーが発生します。

error: incomparable types: String and StringBuilder

一般的には toString メソッドで StringBuilder クラスの変数を String クラスに変換するのが普通かと思います。

public class Sample {
    public static void main(String args[]) {
        String str = "abcdef";

        StringBuilder sb = new StringBuilder();
        sb.append("abcdef");

        System.out.println("str: \"" + str + "\", sb: \"" + sb + "\"");
        System.out.println("== 演算子の結果: " + (str == sb.toString()));
        System.out.println("equals メソッドの結果: " + str.equals(sb.toString()));
    }
}

当然ですが、この場合 String クラスの結果と一致します。

str: "abcdef", sb: "abcdef"
== 演算子の結果: false
equals メソッドの結果: true

 

String クラスで1つ注意を・・

以下の結果は == 演算子の結果も true となります。

これは "abc" と言う文字列を変数 str1 と str2 で使い回すことが出来るからです。

Java では ""(ダブルクォーテーション)で初期化した文字列は  JVM などの仮想マシンのメモリ上で生成されて、同じ文字列を変数に代入しようとした時に使い回してくれるらしいです。

参考:Javaにおける文字列の生成 - new String()は使用しない

public class Sample {
    public static void main(String args[]) {
        String str1 = "abc";
        String str2 = "abc";

        System.out.println("str1: \"" + str1 + "\", str2: \"" + str2 + "\"");
        System.out.println("== 演算子の結果: " + (str1 == str2));
        System.out.println("equals メソッドの結果: " + str1.equals(str2));
    }
}

実行結果は以下のようになります。

str1: "abc", str2: "abc"
== 演算子の結果: true
equals メソッドの結果: true

 

まとめ

今回の String クラスと StringBuilder クラスの == 演算子と equals メソッドの結果を表にまとめます。

  String クラス StringBuilder クラス
== 演算子  同一性  同一性
equals メソッド  同値性  同一性

 

※ あくまで今回の結果であることに注意してください。

みなさんが開発中のプロジェクトでは StringBuilder クラスの equals メソッドが同値性を判定できるようにオーバーライドされている可能性があります。

ちなみに String クラスの equals メソッドが同値性を判定できるのは、 String クラス内で equals メソッドが同値性を判定できるようにオーバーライドしているからです。

String (Java Platform SE 8)