
これだけ覚えよう! 初心者必見、JavaのStringの基本と応用をご紹介
Stringとは文字が集まってできた「文字列」のことで、Javaで扱えるデータの中では基本的なものの一つです。
文字列、つまりStringを使わないJavaプログラムはないと言ってもいいでしょう。
ですから、Stringの特徴や上手な使い方を知っていると、プログラミングがとてもやりやすくなるのです。
上手なプログラムは、プログラム中でのStringの使い方も「ほほぅ…」と言いたくなるほど上手なものですよ。
この記事では、JavaでのStringがどういうものか、どういう風に使えるものかをお伝えします。
そもそもStringとはなんだろうという所から、Stringを使う上では欠かせない基本的なメソッドの使い方まで広くご紹介しますし、最近のJavaではStringがどんなに便利になったかも、しっかり分かりますよ!!
※この記事のサンプルは、Java 11の環境で動作確認しています
目次
1.Stringとはどんなものか
1-1.Stringとは文字が集まった“文字列“のこと
JavaのStringとは文字が集まったモノのことで、いわゆる文字列です。Javaでは、A、B、C、あ…のような文字一つを、基本データ型のchar一つで表しますよね。Stringはそんな文字がたくさん集まったモノです。
1-2.Javaの文字列はStringクラスのインスタンス
Javaの文字列は、Stringクラスのインスタンスです。クラスですから、文字列という値と、メソッドという振る舞いをString自身が持っています。ですので、Javaでの文字列操作は、文字列そのものに働きかけて何かをするのです。
例えば、“ABCD”というStringは、‘A’、‘B’、‘C’、‘D’という四つのcharが集まって順番に並んだものです。Javaでデータの集まりを扱う時はインデックスを使ってアクセスする配列をよく使いますが、Stringにも同じようなインデックスの考え方がありますので、配列に似ているとは言えます。
char[] charArr = { 'A', 'B', 'C', 'D' }; String str = "ABCD";
でも、Stringとcharの配列はまったく違うものです。前述のとおりStringはクラスなので、文字列を扱うための便利なメソッドがたくさん備わっています。データの入れ物でしかない配列や、データそのもののプリミティブ型より、Stringはずっと使いやすいものです。それらのメソッドの使い方は後述します。
1-3.Stringのインスタンスを作るには、文字列を“”で囲う
JavaでStringを作るには“”(ダブルクォーテーション)で文字・文字列をくくります。それだけで、newしなくてもStringのインスタンスを作ったことになるのです。
ですから、あとはStringのインスタンスを指す変数を宣言して参照させれば、もうあとはStringをどんどん使うだけです。このように、JavaではStringはかなり特別扱いされるものなのです。
String str = "ABCD"; String str2 = "あいうえお";
ちなみに、“”でくくって表現した文字列は、文字列リテラル(string literal)とも呼ばれます。文字列そのもの、という意味です。
そして、“”でくくった文字列はそれ自体がStringのインスタンスなので、Stringが持つメソッドをちゃんと呼び出せます。面白いですね。
System.out.println("あいうえお".length()); // → 長さは5
1-3-1.Javaの文字列リテラルで特別な意味を持つ文字
文字列リテラルは始めと終わりに“”を使いますので、この中に“そのものを含めたい場合は、\をその前に置きます。これをエスケープと呼びます。
String str = "Stringに\"を含めてみる"; // → Stringに"を含めてみる
その他にも、以下のようなcharでも使える特殊な文字の指定は、文字列リテラルの中でも有効です。文字をコードポイントで直接指定する\uxxxxも使えます。
String str = "タブ=\t、改行=\n、復帰=\r、改頁=\f、円文字(バックスラッシュ)=\\、バックスペース=\b、あ=\u3042";
1-3-2.Javaの文字列リテラルは“”だけを使う
JavaScriptなど他のプログラミング言語では”(シングルクォーテーション)でくくっても文字列を作れます。ですが、Javaでは”はcharの一文字を表すことに使われますので、以下のプログラムはコンパイルエラーになります。
// コンパイルエラー、''は一つの文字(char)を表現するのに使うもの String str = 'ABCD'; String str2 = 'あいうえお';
1-3-3.Stringのコンストラクタはわかっている人向け
Stringはクラスですしコンストラクタもありますから、new演算子でStringのインスタンスを作ることももちろんできます。でも、Stringのコンストラクタには、いわゆる「文字コード」の知識が必要なものが多いので、あらかじめ勉強してからの方がいいでしょう。簡単な使い方は後述します。
ちなみに、Javaのプログラミングでは、自分でStringをnewする機会はあまりなかったりします。大抵は、メソッドの引数や戻り値のStringをそのまま使うからです。厄介な文字コードも、フレームワークで意識しなくてもいいようにしてくれていることも多いので、学ぶのは必要になってからでもいいでしょう。
1-4.Stringとインデックス
さきほど、Stringとcharの配列は違うものだと書きました。それはその通りなのですが、Stringを扱う上では配列と同様にインデックスの考え方を知っておかないと、Stringのメソッドを使う時に困ったことになります。
といっても、Stringのインデックスは難しいものでは全然なく、要は配列のインデックスと同じです。Stringが持っている文字列の文字一つ一つにアクセスしたり、範囲を指定するには、配列と同じようにインデックスという位置で指定する…というだけなのです。
Stringとインデックスの考え方は、以下の図のとおりになります。配列と同じですね。Excelの関数やSQL、他のプログラミング言語とも同じだと言えます。ただ、JavaのStringではインデックスは0始まりだということは、絶対に覚えておきましょう。
1-5.【参考】なぜ“string”と呼ばれるのか?
なぜこれらの文字の集まりをstringと呼ぶのでしょうか。それは、stringという単語には「ひも」や「糸」の他にも、「ひもに通したもの、数珠つなぎになったもの、一連、ひと続き」(weblib英和辞典)という意味があるからです。つまり、文字列とはstring of charactersである、というわけです。
文字が一列になっている様子が、stringを文字列の意味で使い始めた人にはぴったりだと思えたのでしょう。そして、今のプログラミング言語での意味でのstringの用法は、どうやら1950~60年代には既にあったようですね。
2.【基本】Stringを使ってみる
では、さっそくStringをどんどん使ってみて、Stringがどういうものかを体験していきましょう!
この章では、Java 7までのStringにあるメソッドを、使い方と一緒に簡単に紹介します。プログラムを作る現場ではJava 7以前が使われていることがまだまだ多いので、この章でStringの基本を学べば、どんな場所でもStringを使いこなすための基礎知識が身に着きますよ。
なお、Java 8以降のStringの変更点もこの章の中では軽く触れますが、詳細を記述した別の章を用意しましたので、そちらも読んでみてください。
2-1.文字の数を調べるlength
String.lengthを使うと、Stringが持っている文字の数(=文字列の長さ)がわかります。lengthは、配列ではintの値ですが、Stringではintを戻すメソッドです。戻ってくる数値は、いわゆる全角半角が関係するバイト数ではなく、あくまで文字数です。わかりやすいですね。
String str = "String"; String str2 = "あいうえお"; int length = str.length(); int length2 = str2.length(); System.out.println(length); // → 6 System.out.println(length2); // → 5(Shift_JISの10バイトでも、UTF-8の15バイトでもない)
そして、プログラムを作っていると、Stringが何文字かではなく何バイトか知りたいことがあります。その方法は後述します。
2-1-1.Stringが空かどうか調べるisEmpty
Stringが空か(=文字数が0)どうかの確認には、booleanを戻すString.isEmptyを使いましょう(Java 6以降)。lengthの結果が0か…でもいいのですが、isEmptyならプログラムが(ちょっと)短くなりますし、行っていることの「意味」も分かりやすくなります。
String str = ""; if (str.isEmpty()) { System.out.println("文字列は空です"); } if (str.length() == 0) { System.out.println("文字列は空です"); }
ちなみに、Java 11ではString.isBlankという、文字列が全部空白扱いできる文字かを確認するメソッドが追加されました。isBlankについては後述します。
2-2.部分文字列を切り出すsubstring
Stringから、ある位置からある文字数の文字列を切り出す時には、String.substringを使います。
substringの戻り値は、指定した開始インデックスから終了インデックスの前までにある文字列の、新しいStringインスタンスです。指定するインデックスは、Stringをcharの配列として考えた時の、配列のインデックスの数値です。
String str = "あいうえお"; // [0:'あ', 1:'い', 2:'う', 3:'え', 4:'お'] String str2 = str.substring(1, 3); // インデックス1から、3の前まで String str3 = str.substring(0, 2); // インデックス0から、2の前まで String str4 = str.substring(3); // インデックス3から、最後まで System.out.println(str2); // → いう System.out.println(str3); // → あい System.out.println(str4); // → えお
String.substringについては、以下の記事も参照してください。
関連記事ちなみに、例えばShift_JISで表される文字の2バイト目から切り出してしまった…という悲しい事件は、Javaでは起こりません。何度もお伝えしているとおり、Stringが扱う単位はバイトではなく、文字だからです。
2-2-1.一文字だけ切り出すcharAt
Stringが持つ文字列から、あるインデックスの文字だけ切り出したいなら、charAtも使えます。このメソッドは、指定されたインデックスにある文字を、charで取り出します。
String str = "あいうえお"; // [0:'あ', 1:'い', 2:'う', 3:'え', 4:'お'] char a = str.charAt(0); char i = str.charAt(1); char u = str.charAt(2); char e = str.charAt(3); char o = str.charAt(4); System.out.println(a); System.out.println(i); System.out.println(u); System.out.println(e); System.out.println(o);
なお、文字をintで取り出すcodePointAtというメソッドもあります。でも、このメソッドがなぜcharAtとは別にあるのか、どういう時に使うかは、文字コード…特にUnicodeのサロゲートペアというものの知識が必要になりますので、ここでは触れません。
2-3.文字列を含むか調べる
Stringが持っている文字列に、別の文字列を含んでいるか確認したい時があります。その時には、String.contains/indexOf/matchesなどを使います。
2-3-1.文字列を含むか調べるcontains/startsWith/endsWith
Stringが文字列を含んでいるかだけbooleanで知りたければ、String.containsを使いましょう。
String str = "あいういあ"; // [0:'あ', 1:'い', 2:'う', 3:'い', 4:'あ'] boolean contains = str.contains("う"); System.out.println(contains); // → true
String.containsや、その他の「含む」の調べ方については、以下の記事も参照してください。
関連記事なお、先頭にあることが分かればいいだけならString.startsWith、最後にあることが分かればいいだけならString.endsWithも便利に使えますよ。
String str = "あいうえお"; boolean start = str.startsWith("あいう"); boolean end = str.endsWith("うえお"); System.out.println(start); // → true System.out.println(end); // → true
2-3-2.文字列の出現インデックスを調べるindexOf
Stringの中で、文字列が始まるインデックスを知りたければ、String.indexOf(前から調べる)/lastIndexOf(後ろから調べる)を使いましょう。indexOf/lastIndexOfは、前述したsubstringと組み合わせるのがよくある使い方です。
String str = "あいういあ"; // [0:'あ', 1:'い', 2:'う', 3:'い', 4:'あ'] int index = str.indexOf("い"); int lastIndex = str.lastIndexOf("い"); System.out.println(index); // → 1(前から調べる) System.out.println(lastIndex); // → 3(後ろから調べる)関連記事
2-3-3.文字列をマッチングするmatches
containsやindexOfでは、一つの固定された文字列しか探せません。これを正規表現と呼ばれるパターンで指定するには、String.matchesを使います。
例えば、文字列全体が特定のパターンであるか調べたいなら、以下のようにすればできます。“|”は、または~という意味です。
String str1 = "猫はかわいい"; String str2 = "猫はかわいくない"; boolean match1 = str1.matches("猫は(かわいい|すばらしい)"); boolean match2 = str2.matches("猫は(かわいい|すばらしい)"); System.out.println(match1); // → true System.out.println(match2); // → false
文字列の一部分だけ含めばいいなら、少し書き方を変えます。マッチさせたいところの前後に“.*”(=どんな文字でも0文字以上含む)を付けているのがポイントです。
String str1 = "猫はすごくかわいい、すてきな動物です"; String str2 = "犬は人類の友であるすばらしいパートナーです"; boolean match1 = str1.matches(".*(かわいい|すばらしい).*"); boolean match2 = str2.matches(".*(かわいい|すばらしい).*"); System.out.println(match1); // → true System.out.println(match2); // → true
正規表現を使いこなすと、大変便利にStringとのマッチングが行えます。興味があればぜひ調べてみて、使いこなせるようになりましょう!!
2-4.文字列を変換する
2-4-1.文字列の一部/全部を置換するreplace/replaceAll
Stringが持つ文字列の一部、あるいはすべてを置換するには、String.replace/replaceAllを使います。
replaceは差し替える文字列が最初に出た部分のみ置換した、replaceAllは出現する部分すべてを置換した、Stringの新しいインスタンスを戻します。
String str = "あいうえおあいうえお"; String replaced = str.replace("あ", "A"); System.out.println(replaced); // → Aいうえおあいうえお
String str = "あいうえおあいうえお"; String replaced = str.replaceAll("あ", "A"); System.out.println(replaced); // → AいうえおAいうえお
replaceAllの一番目の引数も、splitと同様に正規表現を指定できます。ですから、置換条件をかなり柔軟に設定できたりします。
String.replace/replaceAllについては、以下の記事も参照してください。
関連記事 関連記事2-4-2.文字列の大文字・小文字変換をするtoLowerCase/toUpperCase
文字列の大文字、小文字を変換するには、String.toLowerCase/toUpperCaseを使うと、変換されたStringが戻ります。
String str1 = "ABC"; String str2 = str1.toLowerCase(); String str3 = str1.toUpperCase(); System.out.println(str2); // → abc System.out.println(str3); // → ABC
2-4-3.文字列の空白文字を削除するtrim
文字列の前後にある空白文字を削除するには、String.trimを使います。
String str = " ABC "; String trimmed = str.trim(); System.out.println(trimmed); // → "ABC"
ただし、trimはいわゆる全角空白を空白として認識してくれず、削除はされませんので、必要なら自分で処理を作る必要があります。
String str = " ABC "; // ABCの前後に全角空白あり String trimmed = str.trim(); System.out.println(trimmed); // → " ABC "、trimは全角空白を削除してくれない
なお、全角空白にも対応したtrimとして、Java 11でString.stripというメソッドが追加されました。それについては後述します。
2-5.文字列を繋げる・分割する
2-5-1.文字列を分割するsplit
Stringを何かの文字列で分割するには、String.splitを使います。splitを呼び出すと、分割した結果のStringの配列が戻ってきます。
String str = "あ,い,う"; String[] splitted = str.split(","); // → ["あ", "い", "う"] System.out.println(splitted.length); // → 3 System.out.println(splitted[0]); // → あ System.out.println(splitted[1]); // → い System.out.println(splitted[2]); // → う
splitの一番目の引数は、matchesと同じく正規表現と呼ばれるパターンを指定するもので、文字列の分割条件をかなり柔軟に指定できるのです。
String.splitについては、以下の記事も参照してください。
関連記事なお、文字列を改行文字で分割したいだけなら、Java 11以降ではString.linesが使えます。こちらの使い方は後述します。
2-5-2.文字列を+演算子で繋げる
String同士を繋げるには、+演算子を使うのが簡単です。String同士への+演算子の結果は、繋ぎ合わせた文字列を持つ、Stringの新しいインスタンスです。
String str1 = "あいうえお"; String str2 = "ABCD"; String str3 = str1 + str2; String str4 = str1 + "1234" + str2; System.out.println(str3); // → あいうえおABCD System.out.println(str4); // → あいうえお1234ABCD
もし自分自身の文字列を繰り返して繋げたい場合は、Java 11以降ではfor文を使う代わりにString.repeatが使えます。複数のStringを何かの文字で繋げるなら、Java 8以降ではString.joinが使えます。これらの使い方は後述します。
なお、+演算子はJavaではかなり特別な演算子で、足し合わせる対象が何かで動きが変わります。この動き方を理解するにはObject.toStringというメソッドを知る必要がありますが、この記事でお伝えしようとしている範囲を超えますので、記載はしません。
2-5-3.文字列をconcatで繋げる
String.concatは、自分自身が持つ文字列と引数のStringの文字列を繋ぎ合わせた、新しいStringのインスタンスを戻します。concatはconcatenateの略で、繋げるという意味です。+演算子との使い分けはあまり気にしなくても良いので、好きな方を使ってください。
String str1 = "あいうえお"; String str2 = "ABCD"; String str3 = str1.concat(str2); String str4 = str1.concat(str2).concat("1234"); System.out.println(str3); // → あいうえおABCD System.out.println(str4); // → あいうえおABCD1234
2-6.文字列を比較する
2-6-1.【超重要】文字列同士が同じか調べるequals
String同士が同じか調べるには、String.equalsを「必ず」使いましょう。比較演算子 == を使うべきケースは、ほぼ考えられません。
ここでString同士が「同じ」であるとは、Stringが持つ文字列が同じだということ…それが自然でしょう。つまり、“ABCD”と“ABCD”は同じであり、“ABCD”と“あいうえお“は違うということで、まあ、当たり前ですよね。
ただ、Javaではその判断に==を使ってはいけません。たまたま上手くいくこともありますが、駄目なことがほとんどです。確実に比較するなら、Stringが持つ文字列が同じか判断してくれるequalsを使います。
String str1 = "ABCD"; String str2 = "ABCD"; String str3 = new String("ABCD"); if (str1 == str2) { System.out.println("str1 == str2"); // こちら } else { System.out.println("str1 != str2"); } if (str1 == str3) { System.out.println("str1 == str3"); } else { System.out.println("str1 != str3"); // こちらになる!! } if (str1.equals(str3)) { System.out.println("str1 equals str3"); // こちら } else { System.out.println("str1 not equals str3"); }
==は、参照型変数へ使った場合は、同じインスタンスかどうかを調べることになります。Stringが持っている文字列同士を調べてはくれないのです。前述のサンプルでは、三番目のStringは新しくnewしていますので、1番目、2番目のStringのインスタンスとは実は別物なのです。
2-6-2.文字列同士の大小を比較するcompareTo/compareToIgnoreCase
文字列同士を比較して、どちらの方が「小さい」「同じ」「大きい」を判断したい時があります。例えば、ソートをしたい時です。
そんな時には、String.compareToを使います。compareToの結果では、小さい(-1以下)、同じ(0)、大きい(1以上)を判断した結果が、intとして戻ってきます。これは、compareToを実行するStringから見て、比較先よりも自分が小さい、同じ、大きいを判断した結果になります。
String str1 = "ABC"; String str2 = "DEF"; int compared = str1.compareTo(str2); System.out.println(compared); // → -1以下の値、'A'は'D'より「小さい」ため
さらに、文字の大文字・小文字を同じものとして比較をしたいなら、compareToIgnoreCaseを使うといいでしょう。
String str1 = "ABC"; String str2 = "abc"; int compared = str1.compareToIgnoreCase(str2); System.out.println(compared); // → 0、"ABC"は"abc"と大文字小文字を無視すれば同一であるため。
compareToや、compareToを使ったソート処理については、以下の記事も参照してください。
関連記事3.【応用】Stringを活用するためにぜひ知っておきたいこと
さて、ここまでがStringを使う上でぜひ押さえておきたいことです。それでもなかなかボリュームがありましたが、大丈夫でしたか? サンプルプログラムを自分でも書いて動かしてみて、ゆっくりじっくりStringの使い方を理解してください。
ここからは、JavaでStringをより活用するために、ぜひ知っておきたいことをお伝えします。
3-1.Stringは自分自身の文字列を変えられない
Stringは、自分自身が持っている文字列を変えられません。これは必ず覚えておきましょう。
Javaでは、文字列の中身を変えたいのなら、中身が変わっているStringのインスタンスを別に新しく作るしかないのです。今まで出てきた中ではString.replaceやtrim、concatがいい例で、substringも似た考え方ですね。
このような、持っている情報を変えられないインスタンスを、不変(Immutable)なインスタンスと呼ぶこともあります。Javaでの不変なインスタンスは、プリミティブ型のラッパークラス(IntegerやBooleanなど)が代表例です。その他にもLocalDateやPathなど、近年APIへ追加されたクラスはその傾向が強いです。
3-1-1.Stringの内容を変えられないのはワケがある
なぜJavaではStringの内容を変えられないのでしょうか。例えば、C言語で文字列としてcharの配列を使うなら、中身は自由に書き換えられますよね。
以下がStringの内容を変えられない理由として挙げられるものです。
- メモリの利用を効率化する:同じ文字列なら同じメモリ領域を使ってデータを共有できるので、メモリ全体を効率的に使えます。
- 安全性を高める:例えば、パスワードなどを持っているStringの内容を、誰かにいつの間にかこっそり変えられては困りますよね。
- 同期を不要とする:内容を変えられると、マルチスレッド環境などでStringへ同時にアクセスがある状態でのプログラミングが難しくなります。
また、文字列を実装するのに使われる配列や、それらを使うためのいわゆるポインタの操作は、色々なプログラミング言語でのセキュリティ問題の根本原因です。そういうものをJavaの文字列操作に持ち込みたくなかったのかもしれません。
3-1-2.文字列リテラルは同じStringのインスタンスになる
Java上での文字列リテラル、つまり“”でくくって作ったStringのことですが、これは同じ文字列ならJavaのメモリ上でも同じStringのインスタンスです。つまり、String.equalsの項でも例示したとおり、以下のように比較演算子==でも等しい、すなわち同じインスタンスになるのです。
String str1 = "ABCD"; String str2 = "ABCD"; if (str1 == str1) { System.out.println("\"\" == null"); // こちら!! } else { System.out.println("\"\" != null"); }
こうなるのは、Javaがメモリを効率的に使いたいからです。一つの文字列リテラルに固定のメモリ領域を与えておけば、同じ文字列リテラルに対して同じStringのインスタンスを使いまわせます。もし、Stringの内容が自由に変更出来たとしたら、こうはいかないですよね?
3-2.nullと“”は違うもの
例えば、とある超メジャーなデータベースでは、NULLと文字数0の文字列(”)は同じものです。でもJavaでは、nullと文字数0のString(“”、いわゆる空文字列)は違うものなので、プログラム上でもきちんと区別をしなければいけません。
nullは変数がStringのインスタンスを指していない状態、空文字列はStringのインスタンスはあるけれど、持っている文字数が0の状態です。
String strZero = ""; String strNull = null; if (strZero == strNull) { System.out.println("\"\" == null"); } else { System.out.println("\"\" != null"); // こちら!! } if (strZero.equals(strNull)) { System.out.println("\"\" equals null"); } else { System.out.println("\"\" not equals null"); // こちら!! }
ですから、他のJavaプログラマや特にデータベースの知識がある人と会話をする時は、「ヌル」とか「未設定」という感じの言葉が出て来たら要注意です。それらの言葉が意味するものがお互いに違っていれば、それだけでバグの原因になってしまいますので、しっかりと意識合わせをしましょう!!
3-3.Stringと他のクラスを変換する
Stringをintなどの数値にしたり、逆にintなどをStringにしたいことがあります。例えば、前者はファイルなどから読み込んだ値を数値に変換する時、後者は逆にファイルなどへ数値を出力するために書式を付ける場合などです。
3-3-1.String→何かはvalueOfを使う
変換先がプリミティブ型(int/boolean等)、あるいはプリミティブ型のラッパークラス(Integer/Boolean等)なら、それぞれのラッパークラスのvalueOfを使いましょう。
byte b = Byte.valueOf("0"); short s = Short.valueOf("1"); int i = Integer.valueOf("2"); long l = Long.valueOf("3"); float f = Float.valueOf("1.1"); double d = Double.valueOf("2.2"); boolean bl = Boolean.valueOf("true");
charだけは特別で、Stringが引数のvalueOfはありません。String.charAtや、toCharArrayを使います。
char c = "あ".charAt(0); // → 'あ' char[] chaArr = "あいうえお".toCharArray(); // → ['あ', 'い', 'う', 'え', 'お']
また、色々なクラスのコンストラクタには、Stringを引数に取ってインスタンスを作る上での元情報として解釈してくれるものがあります。それらのクラスのJavadocをよく読んでみると、色々な発見があると思います。
java.math.BigDecimal bd = new java.math.BigDecimal("10.1"); // → 10.1を値に持つBigDecimal java.io.File file = new java.io.File("/A/B/C.txt"); // → /A/B/C.txtを値に持つFile
3-2-2.何か→StringはtoStringか、String.valueOfを使う
変換元がプリミティブ型(int/boolean等)、あるいはプリミティブ型のラッパークラス(Integer/Boolean等)なら、それぞれのラッパークラスのtoStringを使いましょう。あるいは、StringにもvalueOfがあり、そちらで変換しても大丈夫です。
Byte.toString((byte) 0); // → "0" Short.toString((short) 1); // → "1" Integer.toString(2); // → "2" Long.toString(3); // → "3" Float.toString(1.1f); // → "1.1" Double.toString(2.2d); // → "2.2" Boolean.toString(true); // → "true" Character.toString('c'); // → "c"
String.valueOf((byte) 0); // → "0" String.valueOf((short) 1); // → "1" String.valueOf(2); // → "2" String.valueOf((long) 3); // → "3" String.valueOf(1.1f); // → "1.1" String.valueOf(2.2d); // → "2.2" String.valueOf(true); // → "true" String.valueOf('c'); // → "c"
数値や日時などの書式を指定したいなら、String.formatや、DecimalFormat、SimpleDateFormat(あるいはDateTimeFormatter)などを使います。String.formatはC言語でのsprintfの使い方に近く、書式のルールも大体同じなので、C言語の経験があれば使いやすいでしょう。詳細はJavadocを参照してください。
String.format("%05d", 1); // → "00001" new java.text.DecimalFormat("#,###").format(1234567); // → "1,234,567" new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date()); // → "2019-02-06" java.time.format.DateTimeFormatter.ISO_DATE.format(java.time.LocalDate.now()); // → "2019-02-06"
3-4.StringBuilderで文字列を繋げる
StringBuilderを使うと、繋げたい文字列を一時的に溜めておいて、終わったら文字列全体のStringを作れます。特に、ループ処理で文字列結合をしたり、他の処理との間で繋げている途中の文字列の受け渡しをするなら、StringBuilderを使うのが良いでしょう。
StringBuilderにStringを追加するにはappendを、全部繋がったStringを得るにはtoStringを呼び出します。
StringBuilder builder = new StringBuilder(); builder.append("あいうえお").append("ABCD"); String str = builder.toString(); System.out.println(str); // → あいうえおABCD
StringBuilder.appendは、StringBuilder自身のインスタンスを戻します。ですのでこのようにappendをいくらでも繋げて呼び出せるのです。このスタイルはJavaのプログラミングではごく普通に見られますので、慣れておくといいですよ!
StringBuilder builder = new StringBuilder(); for (int i = 0; i < 5; i++) { builder.append("あ"); } String str = builder.toString(); System.out.println(str); // → あああああ
なお、この用途には、以前はStringBufferを使っていました(Java 1.4以前)。ですが、今ではほとんどのケースでStringBuilderが適しています。マルチスレッド環境で用いるなどの特殊ケース以外は、StringBuilder一択でいいでしょう。
3-5.文字コードとString
Javaでは文字列を、UnicodeのUTF-16という文字コードの仕様に従った形式で、内部的に保持・利用しています。ただ、Javaでプログラムを書いている際はそれを気にすることはあまりないと思います。
いわゆる文字コードは、Javaの外部からデータを読み込んだ時、Javaから外部へデータを書き出す時に意識することが普通でしょう。もちろん、JavaのStringには、そのような要求にもばっちり応えるためのAPIがあるので、ご安心を。
ここでは、Stringと文字コードについて少しだけお伝えします。
3-5-1.このStringは何バイト?
「このStringは何バイト?」に答えるには、「どのエンコーディングの場合ですか?」の逆質問にまず答えてもらわなければなりません。エンコーディングにより、一文字のバイト数が違うからです。
エンコーディングとは、堅苦しい言葉を使うと「文字符号化方式(character encoding scheme)」というもので、文字を数値として表現する方法のことです。日本人エンジニアにとって身近なものでは、JIS、Shift_JIS、EUC-JP、UTF-16、UTF-8などがあります。
String.getBytesをエンコーディングを指定して呼び出すと、その文字列のバイト配列が得られます。バイト配列の長さをlengthで調べれば、バイト数が分かります。エンコーディングの指定は以下の例のようにStringでもいいのですが、専用のクラスjava.nio.charset.Charsetを使ってもいいでしょう。
String str = "あ"; byte[] utf32 = str.getBytes("UTF-32"); // → [0x00 0x00 0x30 0x42] byte[] utf16 = str.getBytes("UTF-16BE"); // → [0x30 0x42] byte[] utf8 = str.getBytes("UTF-8"); // → [0xe3 0x81 0x82] byte[] sjis = str.getBytes("Shift_JIS"); // → [0x82 0xa0] System.out.println(utf32.length); // → 4バイト(UTF-32) System.out.println(utf16.length); // → 2バイト(UTF-16) System.out.println(utf8.length); // → 3バイト(UTF-8) System.out.println(sjis.length); // → 2バイト(Shift_JIS)
3-5-2.バイナリデータからStringを作る
文字列のバイナリデータからStringを作るには、バイト配列とエンコーディングを指定できるStringのコンストラクタを使います。
byte[] utf32 = { 0x00, 0x00, 0x30, 0x42 }; // あ(UTF-32) byte[] utf16 = { 0x30, 0x42 }; // あ(UTF-16BE) byte[] utf8 = { (byte) 0xe3, (byte) 0x81, (byte) 0x82 }; // あ(UTF-8) byte[] sjis = { (byte) 0x82, (byte) 0xa0 }; // あ(Shift_JIS) String strUtf32 = new String(utf32, "UTF-32"); // UTF-32のバイナリデータから、UTF-32のルールでStringを作る String strUtf16 = new String(utf16, "UTF-16BE"); // UTF-16BEのバイナリデータから、UTF-16BEのルールでStringを作る String strUtf8 = new String(utf8, "UTF-8"); // UTF-8のバイナリデータから、UTF-8のルールでStringを作る String strSjis = new String(sjis, "Shift_JIS"); // Shift_JISのバイナリデータから、Shift_JISのルールでStringを作る System.out.println(strUtf32); // → あ System.out.println(strUtf16); // → あ System.out.println(strUtf8); // → あ System.out.println(strSjis); // → あ
バイナリデータのエンコーディングと、Stringのコンストラクタで指定するエンコーディングは同じでないといけません。同じでなければ、JavaがバイナリデータをどうやってStringに変換すればいいのかわからず、文字化けするか“?”になってしまいます。
String str = "あ"; byte[] utf16 = str.getBytes("UTF-16BE"); // StringをUTF-16のバイナリデータに変換して、 String strUtf8 = new String(utf16, "UTF-8"); // UTF-16のバイナリデータをUTF-8で解釈すると、 System.out.println(strUtf8); // → 0B、"あ"にはならない
バイナリデータから文字列のエンコーディングを直接判別するのは、残念ながら簡単ではありません。ですので、普通はプログラムを作る時に事前に取り決めることになります。あるいは、HTTPなどの通信プロトコルのように、データ通信時にエンコーディングは何か、都度相手から教えてもらうかです。
4.【便利】ここ最近のString事情(Java 8~11)
さて、ここまででStringでぜひ知っていただきたいことをお伝えしてきました。
StringはJavaの仕組みを根底から支える大変大事なクラスですが、改良が止まっているわけではありません。最近もいくつかのメソッドが追加されて、よりかゆいところに手が届くようになっています。
ここでは、Java 8以降で追加されたStringの便利なメソッドたちを紹介します。とても使い勝手の良いものが揃っていますよ。
4-1.Stringを繋げるjoin【Java 8~】
いくつかのStringを何かの文字列で繋げてより大きな別の文字列を作ることは、プログラムでは頻繁に行います。例えば、StringのListを“,”で繋げて、CSV(Comma-Separated Values)にするとかですね。
でも、以前のJavaの標準APIでは、そんな繋げる処理が簡単にできませんでした。ですので、for文とStringBuilderなどを使って繋げるメソッドを自分で作ったものです。
Java 8では、待望のString.joinが追加されました。使い方は超簡単、超直感的です。ちなみに、区切り文字がいらないなら“”にすればいいだけです。
String str = String.join(",", "あ", "い", "う", "え", "お"); System.out.println(str); // → "あ,い,う,え,お"
String[] array = {"あ", "い", "う", "え", "お"}; String str = String.join(",", array); System.out.println(str); // → "あ,い,う,え,お"
List<String> list = Arrays.asList("あ", "い", "う", "え", "お"); String str = String.join(",", list); System.out.println(str); // → "あ,い,う,え,お"
4-1-1.StringJoinerの使い方
String.joinだと、間に挟み込んで何かを繋げるだけです。動的にプログラムをする必要があるなら、java.util.StringJoinerを使ってみてもいいでしょう。
StringJoiner joiner = new StringJoiner(","); joiner.add("あ"); joiner.add("い"); joiner.add("う"); String str = joiner.toString(); System.out.println(str); // → "あ,い,う"
StringBuilderを使う場合に比べると、“,”を挟み込む処理(条件を考えると結構面倒くさい!!)を書かなくてもいいので、それだけでも楽ですね。
繋げた文字列の前後に文字列を追加したい場合も簡単で、StringJoinerのコンストラクタでそれぞれ指定すればいいだけです。これでプログラムミスが減らせそうですね。
StringJoiner joiner = new StringJoiner("],[", "[", "]"); joiner.add("あ"); joiner.add("い"); joiner.add("う"); String str = joiner.toString(); System.out.println(str); // → "[あ],[い],[う]"
4-2.文字のStreamを取得するchars【Java 9~】
Stringが持っている文字単位で何か処理をしたい、そういう場合に文字をIntStreamとして取得できるメソッドcharsが、Java 9で追加されました。
String str = "あいうえお"; str.chars().forEach(i -> System.out.println((char) i));
4-2-1.【発展】コードポイントのStreamを取得するcodePoints【Java 9~】
String.charsは、あくまでchar単位でのStreamです。そのため、サロゲートペアで表現される範囲の文字も、上位サロゲートと下位サロゲートは独立した文字として扱います。
サロゲートペアの範囲の文字も一つの文字のコードポイントとしてStreamで扱いたいなら、同じくJava 9で追加されたString.codePointsを使います。
String str = "?野屋"; // 最初の文字はいわゆる「つちよし」、Unicodeではサロゲートペアの範囲の文字 str.codePoints().forEach(i -> System.out.println(i)); // 3回printされるので、サロゲートペアを正しく認識できている
4-3.【発展】Java 9でのString関連変更
Java 9ではString関連の変更がいくつかありました。以下の外にもありますが、Stringを使ったプログラムには直接的な関係はないので、説明は省きます。
StringのAPIとしては変更はないので使い勝手はそのままです。でもJava 9以降に切り替えれば、もしかすると少しはメモリ使用量が減ったり、実行速度が速くなるかもしれませんね。
4-3-1.Stringの内部保持データ形式の変更【Java 9~】
Stringは、Java 8までは内部でもcharの配列で文字列を保持していました。Javaでの一文字であるcharは2バイト(16ビット)ですが、いわゆる半角英数字の範囲だけなら一文字は1バイト(8ビット)で足りるので、メモリが無駄に使われているとみなされていました。これは欧米圏のプログラムでは問題だったようです。
Java 9での「JEP 254: Compact Strings」にて、String内部のデータ保持方法がbyte配列に変更されました。byte配列になったことで、Stringが持つ文字列が一文字1バイトの範囲だけである場合と、一文字にマルチバイトが必要な範囲である場合とで、データの持ち方が柔軟に変えられるようになったのです。
4-3-2.Stringを+する時の処理方式の変更【Java 9~】
Stringを+した時は、Java 8以前ではStringBuilder.appendを使って文字列を繋げるよう、プログラムの読み替えがコンパイル時に行われていました。
Java 9からは、+の具体的な処理内容をJavaが実行時に決定できるようになりました(JEP 280: Indify String Concatenation)。変更の理由は、文字列連結処理に性能改善の余地を作るためです。文字列連結がStringBuilderに依存していては、もっといいやり方があったとしても性能改善が出来ないのです。
例えば現在の実装の一つは、文字列連結に必要な長さのbyte配列を動的に用意し、そこに文字列をコピーして新しいStringを作る、というもののようです。でも、もっといい方法が実装されれば、これからはJavaの実行環境を変更するだけで性能がアップする可能性があるのです。
4-4.改行コードで分割するlines【Java 11~】
String.splitの特殊形として、改行コードで分割するものがJava 9で追加されました。なかなか優れもので、改行コードの種類もちゃんと見てくれます。
String str = "あ\rい\nう\r\nえ\nお"; str.lines().forEach(System.out::println);
4-5.空白しかないかを確認するisBlank【Java 11~】
Java 11からのString.isBlankを使うと、文字列が空白だけからなるものか調べてくれます。isBlankのいいところは、いわゆる全角空白も空白としてチェックしてくれることです。
String str = " \t\r\n"; // 全角空白が混ざっています boolean blank = str.isBlank(); System.out.println(blank); // → true
ではここで言う空白とは何か、が大事なところです。厳密には、Unicodeで空白であるとグループ化されている文字群とその他いくつかになります。その中に全角空白も入っているということですね。感覚的に空白扱いしたい文字は、大体入っていると思います。
詳しくは、Character.isWhitespaceのJavadocを参照してください。
4-6.全角空白も削除するstrip/stripLeading/stripTrailing【Java 11~】
プログラムでよくあるのが、Stringの前後にある余計な空白(扱いしたい文字)を削除することです。
普通はString.trimを使うのですが、残念ながらtrimしてくれる文字はかなり制限されています。例えば、日本語環境では全角空白も削除したいのにtrimではしてくれないので、仕方なく自分でString.substringを使って処理を作った…という方も多いでしょう。
そんな人へのお勧めがString.stripです。仲間としてstripLeading/stripTrailingもあります。
String str = " あいうえお "; // 前後に全角空白が混ざっています String stripped1 = str.strip(); String stripped2 = str.stripLeading(); String stripped3 = str.stripTrailing(); System.out.println(stripped1); // → "あいうえお" System.out.println(stripped2); // → "あいうえお " System.out.println(stripped3); // → " あいうえお"
削除される文字は、isBlankと同様にCharacter.isWhitespaceがtrueを戻す文字です。詳細はJavadocを参照してください。
4-7.Stringを繰り返すrepeat【Java 11~】
同じ文字列を繰り返したい時、for文をその回数だけ使って文字列を連結するのが普通だと思います。Java 11では、そのために使えるString.repeatが追加されました。
String str = "あいうえお"; String repeated = str.repeat(3); System.out.println(repeated); // → "あいうえおあいうえおあいうえお"
5.さいごに
この記事では、JavaのStringの基本と発展に関する知識、Java 8~11までの主な変更点をお伝えしました。
Javaに限らず、文字列はプログラミングにおける基本的なデータ構造の一つです。そして、メールも、チャットも、この記事も、HTMLもXMLもJSONも全て文字列です。HTTPなどのネットワークプロトコルでは、サーバ間のやりとりは文字列です。データベースへも文字列で情報を保存し、取り出せたりします。
ですから、プログラミングの一つの側面は、プログラム上で文字列をどう作り込むか、プログラムの外部から与えられた文字列をどう解釈するか、文字列をどう加工すれば意味のあるものに効率的に変換できるか…であるとも言えるでしょう。すなわち、文字列を制するものは、プログラミングを制するのです。
なお、この記事では深く触れませんでしたが、WEBなどの多言語環境を前提としたプログラミングでは、いわゆる文字コードの知識(文字集合、文字符号化方式など)が強く求められます。Javaでの文字列操作のやり方と同時に、そのような文字の仕組みについても知識を増やしていっていただきたいです。
皆さんがStringでの文字列操作に慣れることで、Javaで有益・効率的なプログラミングができるようになることを願っています。
コメント