【Java入門】第12回 文字列クラス(String)の利用|文字列の操作や比較の方法
2024.06.21
一般的な業務システムでは、顧客の氏名や住所、商品名などの情報を画面に表示したり、データベースに格納する場合などに必ず文字列を使用するため、最も利用するオブジェクトと言えます。当記事では、Javaで文字列を扱うStringクラスについて、基本的な扱い方や比較の方法についてご説明します。
目次
Javaの文字列クラス(Stringクラス)
Stringクラス
Javaで文字列を扱う際は、Stringクラスの利用が基本となります。
文字を表すchar型のように、プリミティブ型として文字列を扱えるものはJavaのデータ型では準備されていません。(参考:第8回 変数とデータ型)
このため、文字列の変数を利用する際は、String型(参照型)の変数として宣言する必要があります。
文字列とは
そもそも文字列とは何か、という問いに完璧な回答はないと思いますが、JavaのStringクラスに関しては「順序のある文字の集合」を格納するためのクラス、と考えるとわかりやすいのではないでしょうか。
例えば「Hello」という文字列は「H」「e」「l」「l」「o」の5つの「文字」が、決められた順序で並んでいる状態です。
「A」という文字列も、「A」という1つの「文字」が並んでいる状態と言えますので、これも文字列として扱うことができます。(1つしか文字がないので、順序も1つしかありませんが)
String strHello = "Hello"; // 5文字(H → e → l → l → o の順)の文字列
String strA = "A"; // 1文字だけの「文字列」
また、この「文字の集合」には「何もない」という状態(数学的に「空集合」とも言います)が含まれますので、String型も「文字が1つもない(空の)文字列」を定義することが可能です。
この「文字が1つもない文字列」のことを空文字列と言い、これもStringクラスのインスタンスとして生成されます。
String strEmpty = ""; // 空文字列(長さがゼロの文字列)
また、String型の変数は参照型の変数ですので、null(参照なし)を代入することも可能です。
これは空文字列と異なり、参照するStringクラスのインスタンスがない状態を示します。
String strNull = null; // 参照なし(参照先インスタンスがないので長さもなし)
これらの変数の状態を図にまとめると、以下のようなイメージになります。
null・空文字列・スペース
上記に示したとおり、Stringクラスでは null と空文字列は「参照しているインスタンスの有無」に明確な差があります。また、Javaでは空白(スペース)も1つの文字として扱いますので、空文字列("")と空白のみの文字列(" ")も、異なるものとして扱われます。
下記の例では、空文字列や、空白のみの文字列の各パターンに対して、Stringクラスのメソッドを使用して「文字列の長さ(lengthメソッド)」「空文字列かどうか(isEmptyメソッド)」「空白かどうか(isBlankメソッド)」をそれぞれ確認しています。
String empty = ""; // 空文字列
String blank = " "; // 半角スペース1個
String blankZenkaku = " "; // 全角スペース1個
String blankMulti = " "; // (半角スペース+全角スペース)×2
// 空文字
System.out.println(empty.length()); // 0
System.out.println(empty.isEmpty()); // true
System.out.println(empty.isBlank()); // true
// 半角スペース
System.out.println(blank.length()); // 1
System.out.println(blank.isEmpty()); // false
System.out.println(blank.isBlank()); // true
// 全角スペース
System.out.println(blankZenkaku.length()); // 1
System.out.println(blankZenkaku.isEmpty()); // false
System.out.println(blankZenkaku.isBlank()); // true
// 半角スペース全角スペースの順に2個ずつ
System.out.println(blankMulti.length()); // 4
System.out.println(blankMulti.isEmpty()); // false
System.out.println(blankMulti.isBlank()); // true
上記の結果から、空文字列は長さ0で、空文字列でも空白文字列でもある特別な状態であることがわかります。その他の空白文字列は、スペースの数だけの長さを持ち、空文字列とはみなされていません。
また、nullが代入されている変数は参照すべきインスタンスを持たないため、Stringクラスのメソッドが実行できないことに注意してください。
String nullStr = null;
// null
System.out.println(nullStr.isEmpty()); // 実行できない
上記のコードを動作させると実行時エラーとなり、以下のメッセージが出力されます。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "nullStr" is null
※メッセージの意訳:「mainの実行時に例外(NullPointerException)が発生しました:変数 "nullStr" がnullのため、"String.isEmpty()" は実行できません」
このNullPointerExceptionは、参照がない(null)変数に対して、インスタンスのメソッドを呼び出した際には必ず発生します。
lengthメソッドやisBlankメソッドなど、StringクラスのisEmpty以外のメソッドも全て実行できません。
Stringクラスの連結(結合)
+ 演算子の利用
前回までのサンプルプログラムでも特に断りなく利用していましたが、文字列は「+」演算子を利用して連結することができます。
文字列同士や、プリミティブ型の値とも連結ができ、最も簡単に利用できる方法ですが、連結する対象ごと動作について少し説明します。
文字列同士の連結
「+」演算子は数値型の値を加算する演算子ですが、String型のインスタンスに限り、文字列の連結を行うためにも利用することができます。
「+」演算子で文字列を連結する場合は、演算子の左辺 ⇒ 右辺の順に文字列が連結されます。これは、3個以上の文字列を同時に連結した場合も同じです(左側から順に連結されます)。
下記の例では、String型の変数に対して、文字列リテラルと他のString型の変数を「+」演算子でまとめて連結しています。
String s1 = "こんにちは";
String s2 = "読者さん。";
String str = s1 + "、" + s2; // + 演算子を用いて文字列変数2個と文字列リテラルを連結
System.out.println(str); // 「こんにちは、読者さん。」が出力される
クラス型変数(インスタンス)との連結
クラス型の変数(インスタンス)に対して「+」演算子を利用した場合も、文字列として連結することができます。
この場合、連結対象のクラスが「toStringメソッドで返す文字列」が連結されます。
String bdHeader = "BigDecimalの値:";
BigDecimal bd = new BigDecimal(10);
String bdStr = bdHeader + bd; // 文字列変数とクラス型変数を + で連結
System.out.println(bdStr); // 「BigDecimalの値:10」が出力される
// toStringメソッドを明示的に呼び出しても同じ結果となる。
String bdToStr = bdHeader + bd.toString();
System.out.println(bdToStr); // 「BigDecimalの値:10」が出力される
toStringメソッドで返される文字列は対象のクラスの実装によって異なりますので、必ず想定どおりの文字列とならない可能性がありますので注意してください。
上記の例では、BigDecimalクラスのtoStringメソッドが、内部に保持する数値の文字列を返していることがわかります。
※「+」演算子で連結時の toString メソッド実行を検証するサンプル
まず、toStringメソッドを、独自の実装としたクラスを準備します。
(toStringメソッドはObjectクラスで定義されていますので、それを上書きして特定の文字列を返すクラスを作成します)
下記で示すObjectExクラスは、Objectクラスを継承し、toStringの上書き以外の実装を持たないクラスです。
toStringメソッドが呼び出されたとき「ObjectExクラスのtoStringが実行されました。」と出力し、メソッドの呼び出し元には「これはObjectExクラスのインスタンスです。」という固定の文字列を返します。
/**
* ObjectExクラス
*/
public class ObjectEx {
/**
* toStringメソッド
* ObjectクラスのtoStringを上書きします。
*/
public String toString() {
System.out.println("ObjectExクラスのtoStringが実行されました。");
return "これはObjectExクラスのインスタンスです。";
}
}
次に、このObjectExクラスのインスタンスを、String型の変数と「+」演算子で実際に連結して、どのような結果になるか確認してみます。
/**
* インスタンスを文字列と結合した時の動作確認クラス
*/
public class ConcatTest {
/**
* mainメソッド
* @param args
*/
public static void main(String[] args) {
ObjectEx obj = new ObjectEx();
String strPlus = "+演算子での連結結果:" + obj;
System.out.println(strPlus);
String strToStr = "toStringのした文字列との連結結果:" + obj.toString();
System.out.println(strToStr);
}
}
実行結果:
ObjectExクラスのtoStringが実行されました。
+演算子での連結結果:これはObjectExクラスのインスタンスです。
ObjectExクラスのtoStringが実行されました。
toStringのした文字列との連結結果:これはObjectExクラスのインスタンスです。
「+」演算子で別のインスタンスを連結したときも、toStringメソッド内の処理が実行され、「(連結したインスタンスの)toStringメソッドの実行結果の文字列」が連結されていることが分かります。
nullとの連結
例外として、nullリテラルや参照先がnullの変数を、String型の文字列と「+」演算子で連結した場合、「null」という文字列として連結されてしまいます。
※Oracle JDK および Open JDK (ともにバージョンはJava 21)で確認済
この場合、"null"という文字列と見分けがつかなくなるため注意してください。
以下のサンプルプログラムでは、9~11行目のコードでは全て「変数の値はnullです」という文字列が出力されます。13行目では『"null"という文字列』と『nullリテラル』を連結した結果、「nullnull」という文字列が出力されます。
import java.math.BigDecimal;
public class TestNullStr {
public static void main(String[] args) {
String nullValueStr = "null"; // 「null」という文字列
BigDecimal nullBd = null; // 参照先なし(null)
System.out.println("変数の値は:" + null + "です"); // nullリテラルを連結
System.out.println("変数の値は:" + nullValueStr + "です"); // 文字列"null"と連結
System.out.println("変数の値は:" + nullBd + "です"); // null参照変数と連結
System.out.println(nullValueStr + null); // 文字列"null"とnullリテラルを連結
}
}
実行結果:
変数の値は:nullです
変数の値は:nullです
変数の値は:nullです
nullnull
プリミティブ型変数(プリミティブ値)との連結
「+」演算子では、String型の変数と、プリミティブ型の変数、リテラルを連結することもできます。
また、ラッパークラスのインスタンスも同じように連結することが可能です。
String intHeader = "intの値:";
int i = 100;
String str99 = intHeader + 99; // 文字列変数とintリテラルを + で連結
System.out.println(str99); // 「intの値:99」が出力される
String iStr = intHeader + i; // 文字列変数とプリミティブ型変数を + で連結
System.out.println(iStr); // 「intの値:100」が出力される
// intのラッパークラスでも確認
Integer itgr = i;
String iWrapStr = intHeader + itgr; // 文字列変数とInteger型変数を + で連結
System.out.println(iWrapStr); // 「intの値:100」が出力される
// ラッパークラスのtoStringメソッドを呼び出して連結しても同じ結果になる
iWrapStr = intHeader + itgr.toString();
System.out.println(iWrapStr); // 「intの値:100」が出力される
Java言語では、String型の変数(または文字列リテラル)と、プリミティブ型の値を「+」演算子で連結した場合は、文字列としての連結が行われ、戻り値も文字列になります。
このため、プリミティブ型の値と連結させる文字列が数字だけの文字列であっても、数値の計算結果にはならないことに注意してください。
String str100 = "100";
int int100 = 100;
System.out.println(str100 + int100); // 文字列として連結されるため「100100」と出力
またこれを利用して、プリミティブ型の値を文字列(String型)に変換する必要がある場合は、空文字列("")と連結することで文字列化することが可能です。
以下のサンプルプログラムでは、引数がString型であるメソッドを利用するために、各プリミティブ型の値を空文字列と連結することで文字列に変換したうえで、printValueメソッドの引数に設定しています。
/**
* プリミティブ型の値と文字列(String)の連結動作確認クラス
*/
public class ConvertPrimitive {
/**
* mainメソッド
* @param args
*/
public static void main(String[] args) {
// 各プリミティブ型の変数
boolean b = true;
byte bt = 1;
short s = 10;
int i = 100;
long l = 1000L;
float f = 0.1F;
double d = 0.01D;
char c = 'あ';
// 名前と値を渡して、同じ形式で出力
printValue("b", "" + b);
printValue("bt", "" + bt);
printValue("s", "" + s);
printValue("i", "" + i);
printValue("l", "" + l);
printValue("f", "" + f);
printValue("d", "" + d);
printValue("c", "" + c);
}
/**
* 変数名と値を定められた形式で出力します
* @param name String 変数名
* @param value String 変数の値
*/
public static void printValue(String name, String value) {
// 引数で渡された名前と値を所定の形式に編集して出力する
System.out.println("変数[" + name + "]の値は[" + value + "]です。");
}
}
実行結果:
変数[b]の値は[true]です。
変数[bt]の値は[1]です。
変数[s]の値は[10]です。
変数[i]の値は[100]です。
変数[l]の値は[1000]です。
変数[f]の値は[0.1]です。
変数[d]の値は[0.01]です。
変数[c]の値は[あ]です。
他にプリミティブ型の値を文字列化する方法としては、「String.valueOf」メソッドを利用する、ラッパークラスのtoStringメソッドを利用するなどがありますが、メソッドの動作の確認などを学習する際には、簡潔に記述できるこの方法をお勧めします。
+= 演算子の利用
「+=」演算子で文字列を連結した場合は、両辺の値を連結した文字列が、演算子の左辺の変数に代入されます。連結の内容は、「+」演算子を利用した場合と一緒です。
String s1 = "こんにちは";
String s2 = "読者さん。"
s1 += "、" ; // 「こんにちは」+「、」
s1 += s2; // 「こんにちは、」+「読者さん。」
System.out.println(s1); // 「こんにちは、読者さん。」と出力される
クラス型変数やプリミティブ型の変数の場合も、同様に「+=」演算子での文字列連結が可能です。連結の内容も「+」演算子での連結と同じ結果になります。
String bdStr = "BigDecimalの値:";
String intStr = "intの値:";
BigDecimal bd = new BigDecimal(10);
int i = 100;
bdStr += bd; // += 演算子で、文字列とBigDecimalの変数を連結
intStr += i; // += 演算子で、文字列とプリミティブ型の変数を連結
System.out.println(bdStr); // 「BigDecimalの値:10」と出力される
System.out.println(intStr); // 「intの値:100」と出力される
その他の方法
その他、文字列クラスの結合を行う方法として、Stringクラスのメソッドを利用することもできます。
・concatメソッドの利用
下記の例では、concatメソッドを用いて複数個のString型の変数を連結しています。
concatメソッドでは、メソッドを呼び出すインスタンス(strX)だけでなく、メソッドの引数に渡すインスタンス(strY、strZ)が全てnullではない必要があります。
String strX = "X";
String strY = "Y";
String strZ = "Z";
System.out.println(strX.concat(strY)); // XY
System.out.println(strX.concat(strY).concat(strZ)); // XYZ
いずれかがnullだった場合は、NullPointerExceptionが発生し、実行時エラーとなります。
String strX = "X";
String strY = null;
System.out.println(strX.concat(strY)); // NullPointerExceptionが発生
・joinメソッドの利用(Java 8 以降)
joinメソッドでは、各文字列の間に挟む文字列(デリミタ)を追加して、複数個の文字をいっぺんに連結することができます。連結対象の文字列はnullや空文字列でも動作します。
String strX = "X";
String strY = "Y";
String strZ = "Z";
System.out.println(String.join(",", strX, strY)); // X,Y
System.out.println(String.join(",", strX, strY, strZ)); // X,Y,Z
System.out.println(String.join(",", strX, "", null, strZ)); // X,,null,Z
ただし、第1引数(デリミタ)はnull以外である必要があります。nullが第1引数に渡された場合はNullPointerExceptionが発生し、実行時エラーとなります。
System.out.println(String.join(null, strX, strY)); // NullPointerExceptionが発生
第1引数(デリミタ)には空文字列や、複数個の文字からなる文字列を指定することも可能です。
デリミタに空文字列を指定した場合は、残りのパラメータ文字列を単純に連結したときと同じ結果になります。
System.out.println(String.join("", strX, strY, strZ)); // XYZ
System.out.println(String.join(":::", strX, strY, strZ)); // X:::Y:::Z
・StringBuilderなどのクラスを利用する
Stringクラスに準備されている方法以外にも、StringBuilderといったクラスを利用して文字列を連結する方法があります。この方法は、多くの文字列を結合する場合にメモリの利用量を減らしたり、StringBuilderのメソッドであるinsertメソッドで指定の位置に文字列を差し込んだりする場合に利用されます。
以下のプログラムは、StringBuilderクラスのappendメソッドを利用して文字列を結合するサンプルです。
StringBuilder builder = new StringBuilder(); // 空のStringBuilderを生成
builder.append("こんにちは"); // 文字列"こんにちは"を連結
builder.append("、").append("読者さん。"); // 文字列"、"と"読者さん。"を連結
String sentence = builder.toString(); // Stringの値に変換
System.out.println(sentence); // 「こんにちは、読者さん。」を出力
Stringクラスで行える操作の例
Stringクラスでは、文字列を編集したり、評価するための様々なメソッドが準備されています。
ここではStringクラスの比較の方法やメソッドについて、主だったものをいくつか紹介します。
文字列を比較する
String型は参照型であり、比較演算子の「==」や「!=」では、文字列が同じ内容であるかどうかは正確に比較できません。
(参考:第10回 Javaの演算子(参照型変数の比較))
このため、比較したい内容によって、適した比較方法を利用する必要があります。
nullとの比較
String型を含め、参照型の変数が null であるかかどうかは、比較演算子「==」または「 !=」を利用して確認することが可能です。
String strNull = null;
String strEmpty = "";
// nullかどうかをチェック
System.out.println(strNull == null); // true
System.out.println(strEmpty == null); // false
// 「nullでない」かどうかをチェック
System.out.println(strNull != null); // false
System.out.println(strEmpty != null); // true
文字列同士の比較
・equalsメソッドの利用
文字列の内容が同一かどうかをチェックするには、equalsメソッドを利用して判別することができます。下記の例では、同一内容の文字列を持つStringのインスタンスを2つ作成し、比較演算子とequalsメソッドでの比較を行っています。
String message1 = new String("nice to meet you!");
String message2 = new String("nice to meet you!");
// 同じかどうか
System.out.println(message1 == message2); // false 参照先が同一かどうか
System.out.println(message1.equals(message2)); // true 文字列の内容が同一かどうか
// 異なるかどうか
System.out.println(message1 != message2); // true 参照先が異なるかどうか
System.out.println( ! message1.equals(message2)); // false 文字列の内容が異なるかどうか
// エラーにはならないが必ずfalseになる
System.out.println(message1.equals(null)); // false
参照型では「==」や「!=」の演算子では「参照先が同一かどうか」を比較しますが、equalsメソッドでは「文字列の内容が同一かどうか」を比較します。
また、equalsメソッドの引数に null を指定しても実行時エラーとはなりませんが、メソッドを呼び出している側のインスタンスがnullではないので、結果はfalseとなります。
N番目の文字を取得する
・charAtメソッド
charAtメソッドでは、文字列の指定した位置の「文字」を取得することができます。
Stringクラスの文字の位置(インデックス)は、配列などと同様に「0」から開始されるため、指定可能な範囲は「0番目」から「(文字列の長さ - 1)番目」までになることに注意してください。
下記の例では、文字列「ABCDEFG」の「3番目」を指定してメソッドを呼び出していますが、取得できる文字は「D」になります。
String strAbc = "ABCDEFG"; // 7文字の文字列
char ch3 = strAbc.charAt(3); // 3番目の「文字」を取得
System.out.println(ch3); // D が出力される
charAtメソッドの呼び出し時に不正な文字位置を指定した場合、StringIndexOutOfBoundsExceptionが発生し、実行時エラーとなります。
下記の例では、文字列の範囲外の位置(7)や、位置として不正な値(-1)でcharAtメソッドを呼び出しています。
char ch7 = strAbc.charAt(7); // 7番目は無いため実行時エラー
char chMin = strAbc.charAt(-1); // 不正なインデックスのため実行時エラー
エラーメッセージ:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
文字列の一部を取得する
・substringメソッドの利用
Stringクラスでは、substringメソッドを利用して文字列の一部を取得することができます。
substringメソッドでは、文字列の取得開始位置と終了位置を、文字列の0番目~N番目で指定して取得します。(終了位置を指定しない場合は、開始位置以降のすべての文字列を取得します)
下記の例では、郵便番号の文字列「〒060-0807」から、郵便記号(〒)と郵便番号の前半(060)と後半(0807)をそれぞれ取得しています。
String postcode = "〒060-0807";
String symbol = postcode.substring(0, 1); // 0番目から、1番目より前まで
String region = postcode.substring(1, 4); // 1番目から、4番目より前まで
String detail = postcode.substring(5); // 5番目以降全て
System.out.println(symbol); // "〒"
System.out.println(region); // "060"
System.out.println(detail); // "0807"
charAtメソッドと同様に、開始位置、終了位置の指定が文字列の長さの範囲外であるなど、不正な範囲を指定した場合はStringIndexOutOfBoundsExceptionが発生し、実行時エラーとなります。
下記の例では、文字列の長さより大きい終了位置を指定したり、開始位置が終了位置より後ろになる範囲など、不正な範囲を指定してsubstringメソッドを呼び出しています。
String tooLong = postcode.substring(0, 12); // 不正な範囲(文字列の長さより大きい)
String badIndex = postcode.substring(3, 2); // 不正な範囲(開始位置 > 終了位置)
エラーメッセージ:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Range [0, 12) out of bounds for length 9