Developer

【はじめてのJava】文字列の比較【基本構文編】
2020.12.29
Lv1

【はじめてのJava】文字列の比較【基本構文編】

はじめてのJava

このシリーズでは、初めてJavaやプログラミングを勉強する方向けに、Javaによるプログラミングの基礎を説明していきます。
目標レベルは、Javaの資格試験の一つである「Oracle Certified Java Programmer, Silver」(通称Java Silver)に合格できる程度の知識の習得です。
はじめてJavaやプログラムに触れる方にもできるだけわかりやすい解説を心がけていきます。


基本構文編(条件分岐)

基本構文編(条件分岐)では、ある条件によってプログラムの処理を分岐させることが出来る条件分岐について扱っていきます。

今回は、文字列の比較における注意点について扱っていきます。


目次


文字列の比較

以前、要素を比較すること際に使用する関係演算子について説明しました。
例えば、「int x = 5;」「int y = 5;」と変数に代入した場合について考えてみます。この時、関係演算子「==」を使って「x == y」と記述した条件式はtrueを結果として返します。

関係演算子「==」は左オペランドと右オペランドが等しい場合にtrueを返す演算子です。今回、変数xと変数yの値を比較した結果、二つの変数には共に5という等しい値が代入してあるのでtrueを返します。
このように、「==」を使用すると二つの基本データ型の値が等しいか否かを確認することが出来ます。

一見便利な「==」ですが、比較を行う際に注意があります。
それが今回のタイトルでもある「文字列の比較」をはじめとした、オブジェクト参照型の比較を行う場合です。

基本データ型同士の比較を参考に考えると、文字列も「==」による比較ができそうな気がします。例えば、「String strX(中身は”hello”)」「String strY(中身は”hello”)」があった場合、「strX == strY」は直観的にはtrueとなってほしいと思います。しかしJavaではtrueになるとは限りません。Javaで文字列を扱う際は「String型」を利用しますが、String型がオブジェクト参照型であるためです。そのため、中身の文字列を比較したくても単純に「==」で比較することができません。

==による比較の仕組み

「==」は、基本データ型の変数同士を比較する場合、その変数に格納されている値が等しいか比較して結果を返します。一方、「==」がオブジェクト参照型の変数同士を比較する場合は、 変数同士の参照先が同じかどうか を比較して結果を返します。

以下の図のように、オブジェクト参照型の変数には、データ本体ではなく、データの保存場所が格納されています。(詳しい説明はこちらの記事でしました。)

「==」がオブジェクト参照型のデータを比較する時は、参照しているオブジェクトの値が等しいかどうかではなく、「同じオブジェクトを参照しているかどうか」についての比較を行います。
文字列について扱うString型もオブジェクト参照型です。そのため「==」による比較を行う場合も、同じ文字列が代入されているかどうかではなく、 同じオブジェクトを参照しているかどうか を比較します。

「==」での文字列の比較について、以下のサンプルコードを見てみましょう。

public class Sample1 {
	public static void main(String[] args) {
		String str1 = "こんにちは";
		String str2 = str1; //str2とstr1は同じものを指す。
		String str3 = "こんばんは";
		String str4 = new String("こんばんは"); //str4はstr3とは異なるオブジェクトとなる

		System.out.println("str1:"+str1);
		System.out.println("str2:"+str2);
		System.out.println("str1 == str2の結果は"+(str1==str2)+"です。");

		System.out.println("");

		System.out.println("str3:"+str3);
		System.out.println("str4:"+str4);
		System.out.println("str3 == str4の結果は"+(str3==str4)+"です。");

	}

}

実行して「==」による比較結果を見てみると、str1とstr2はtrueとなっていますが、str3とstr4の場合はfalseとなっています。
これは、str3とstr4がそれぞれ値は等しいものの異なるオブジェクトを参照しているためです。

サンプルコードにおける参照については以下の図のようになっています。

「==」による比較は、str1とstr2のように、「参照先が同一かどうか」という比較になるのです。そのため、「文字列の中身が同じかどうか」という比較にはなりません。


文字列の中身の比較の方法

文字列の中身同士を比較したい場合は、equalsメソッドを使用します。使い方は以下の通りです。

文字列1の変数名.equals(文字列2の変数名)

例えば、サンプルコードの場合、「str3.equals(str4)」のように記述することで、str3とstr4の参照先の文字列を比較することができます。

public class Sample1 {
	public static void main(String[] args) {
		String str3 = "こんばんは";
		String str4 = new String("こんばんは"); //str4はstr3とは異なるオブジェクトとなる

		System.out.println("str3:"+str3);
		System.out.println("str4:"+str4);
		System.out.println("str3 == str4の結果は"+(str3==str4)+"です。"); //falseになる(文字列の参照先の比較)
		System.out.println("str3.equals(str4)の結果は"+ str3.equals(str4)+"です。"); //trueになる(文字列の中身の比較)

	}

}

str3とstr4は参照先のオブジェクトが異なるので「==」による比較の結果はfalseとなりますが、参照しているオブジェクトの値はどちらも「こんばんは」という文字列なので、equalsメソッドで比較した結果はtrueとなります。


サンプルコード

あらためて、文字列の比較について、以下のサンプルコードを見てみましょう。

public class Sample1 {
	public static void main(String[] args) {
		String str1 = "こんにちは";
		String str2 = str1; //str2はstr1と同じオブジェクト
		String str3 = "こんばんは";
		String str4 = new String("こんばんは"); //str4はstr3とは異なるオブジェクトとなる

		System.out.println("str1:"+str1);
		System.out.println("str2:"+str2);
		System.out.println("str1 == str2の結果は"+(str1==str2)+"です。"); //true
		System.out.println("str1.equals(str2)の結果は"+ str1.equals(str2)+"です。"); //true

		System.out.println("");

		System.out.println("str3:"+str3);
		System.out.println("str4:"+str4);
		System.out.println("str3 == str4の結果は"+(str3==str4)+"です。"); //false

		System.out.println("str3.equals(str4)の結果は"+ str3.equals(str4)+"です。"); //true

	}

}

実行結果


ポイント

オブジェクト参照型の変数を「==」で比較する際は、二つの変数がどのオブジェクトを参照しているかを比較している。
String型はオブジェクト参照型なので「==」で比較すると「参照先の比較」となる。
String型の値を比較したい場合は、equalsメソッドを使用する。


コラム:String型の仕組み

サンプルコードは以下のように書くこともできます。

public class Sample1 {
	public static void main(String[] args) {
		String str1 = "こんにちは";
		//String str2 = str1; //変更前
		String str2 = "こんにちは"; //変更後
		String str3 = "こんばんは";
		String str4 = new String("こんばんは"); //str4はstr3とは異なるオブジェクトとなる

		System.out.println("str1:"+str1);
		System.out.println("str2:"+str2);
		System.out.println("str1 == str2の結果は"+(str1==str2)+"です。");
		System.out.println("str1.equals(str2)の結果は"+ str1.equals(str2)+"です。");

		System.out.println("");

		System.out.println("str3:"+str3);
		System.out.println("str4:"+str4);
		System.out.println("str3 == str4の結果は"+(str3==str4)+"です。");

		System.out.println("str3.equals(str4)の結果は"+ str3.equals(str4)+"です。");

	}

}

このコードも、本文中のサンプルソースと同じ結果になります。

このコードをよく見ると、変数str1だけでなく、変数str2も「String str1 = “こんにちは”;」のように、値を直接代入しているように見える表記方法で記述しています。一方で、str4はnewというキーワードを使って記述しています。

本来、オブジェクト参照型は、newをして作成するのが普通です。しかし、String型は基本データ型ではないものの、文字列という非常にたくさん使うデータ型であるため、基本データ型と同じような直接代入に見える表記方法での記述が可能なように設計されたのだと思われます。このString型のnewしない代入については、扱いに少し癖があります。

String型の変数に文字列を代入する場合、その文字列がすでにメモリ上に存在するかどうか確かめます。メモリ上にない場合はその文字列を新たに作成しその場所を参照し、メモリ上の既にある場合は、同じ参照先を指します。

例えばコラムのサンプルでは、変数str1に代入を行った際にはメモリ上に「こんにちは」は無いため、「こんにちは」をメモリ上に作成します。str2に代入を行った際には、既にstr1の際の「こんにちは」が存在するためstr2はこの「こんにちは」を参照するようになります。その結果、str1とstr2は同じオブジェクトを参照するようになり、「==」で比較した結果はtrueとなります。

一方で、変数str4に値を代入する際は、「String str4 = new String(“こんばんは”);」のように記述しています。
このように記述すると、すでにメモリ上に「こんばんは」があるかどうかに関係なく、新しい参照先を準備してそこに「こんばんは」という文字列を保持します。そのため、str4は既にstr3が参照している「こんばんは」とは別の参照先に新しく「こんばんは」という文字列を作成して参照します。そのため、str3とstr4を「==」で比較した結果はfalseとなります。

また、newで使用するメモリ上の領域は=が探索するメモリ上の領域とは異なる領域です。そのため、以下のように、先にnewした場合も、str3とstr4の「==」による比較はfalseになります。

public class Sample1 {
	public static void main(String[] args) {
		String str3 = new String("こんばんは"); //新しく「こんばんは」の領域を確保
		String str4 = "こんばんは"; //newの領域とは異なる領域から「こんばんは」を探す

		System.out.println("str3:"+str3);
		System.out.println("str4:"+str4);
		System.out.println("str3 == str4の結果は"+(str3==str4)+"です。"); //false

		System.out.println("str3.equals(str4)の結果は"+ str3.equals(str4)+"です。"); //true

	}

}

newを使った記述の仕方については、クラスやオブジェクトの項目で詳しく説明していきます。ここではString型の変数は「==」で比較してしまうと期待通りの結果が返ってこないことがあるため、必ずequalsメソッドで比較することを意識できればOKです。


はじめてのJavaシリーズの目次はこちら
基本構文編の記事一覧はこちら


java 11 の練習問題一覧はこちら