[Java]文字列連結で、+演算子 or StringBuilder どちらを使うべき?

 

 

こんにちは。ナガオカ(@boot_kt)です。

 

Javaの文字列の結合は、演算子が良いのか、StringBuilder(or StringBuffer)が良いのか、これもよく聞く問題です

先に結論言っちゃうと、コンパイル時に最適化されるので、演算子を積極的に使うと良いです
むしろ、StringBuilderにしてると最適化が効かず効率が悪いこともあります

とは言え、全ての場面で演算子を使えばいいかと言うとそうでもないんですよねぇ~
StringBuilderを使う方が良い場面もあります

この記事では、javap.exeを使って、文字連結時に使う演算子とStringBuilderの使い分け方の根本的な考え方を示します
 

ちょっと記事の文量が多いように見えますが、読むべき量は意外と少ないので、良かったら最後まで見てあげてください

 

StringBuilderStringBufferについて
StringBuilderStringBufferも使い方は同じですしそれほど大きな違いは無いため、本記事ではStringBuilderのみを取り上げます

ちなみに、StringBuilderStringBufferとの違いは

  • StringBuilder
    スレッド処理なし
    処理早い
  • StringBuffer
    スレッド処理あり
    処理遅い
  • 現実的にはスレッド云々は別途対応して、StringBuilderを使えばいいのかなと思います
    StringBufferを使うメリットがあまり無い?

この記事のザックリした内容
◆対象読者

  • Javaの文字列の結合は演算子さえ使っておけばいいと思っている人
  • Javaの文字列の結合はStringBuilderクラスさえ使っておけばいいと思っている人
  • Javaの文字列の結合の事なんて考えたこと無かった人
  • Javaの知識必要
  • JDKをインストールしている

 

◆解決できるかも知れないお悩み

  • Javaの文字列結合において、演算子とStringBuilderクラスの使い分け方が理解できます

 

◆どうやって解決するか

  • javap.exeを使って、ソースコードの展開方法(バイトコード)を見ます

 

 

 

 

まずは結論! 演算子とStringBuilderクラスとの使い分け方

  1. 基本的には演算子を使えばいいです
  2. 但し、1行で全部の文字列を連結する場合のみ
  3. 何行かに分けて連結する場合は、StringBuilderクラスを使ってね
  4. forとかwhileの中ではStringBuilderクラスを使う方が良いでしょう
  5. どっちを使えばいいか分からない場合は、javap.exeで確認せよ!

 

javapを使ってみよう

javaファイルをコンパイルすると、バイトコードというものに展開されます
バイトコードに展開される内容はソースコードと全く同じというわけではなく最適化されることがありますし使用している命令が変わることもあります

Javaの実行環境であるJVMはそのバイトコードを読み取って実行してくれます
ですので、実際にどのようなコードで動くのかはバイトコードを見るのが良いのです
 

で、そのバイトコードがclassファイルです
※classファイルはバイトコード以外の情報も持ってるよ

javapを使えばclassファイルに入っているバイトコードが見られます

 

まずはjavapの存在を確認

JDKをインストールした時のディレクトリのbinフォルダにjavapはいます

僕の環境だとここ↓

  • C:\Program Files\Java\jdk1.8.0_72\bin\javap.exe

WindowsでもMacでもPATHを通していると思うので、以下のコマンドを打ってみてください

↓こんな感じで何か表示されればOKです

以下のソースコードをコンパイルします

このソースコードをコンパイルしてclassファイルを作ってちょーーーだい!

Main.java
Main.javaは別にコンパイルしなくてもいいですよ
動かしたいのであればコンパイルすればいいですが、SamplePlus.javaとSampleBuilder.javaさえコンパイルしてもらえればOKです

 

javapでclassファイルを読み込みます

テキストファイルに出力したければリダイレクトしてください

こんなデータが出力されます
↓これがバイトコードです

javapで出力された内容を解説します(SamplePlus編)

注目して欲しいのは2つ

  • newの数
  • 演算子がStringBuilderに変換(最適化)されている

演算子での文字連結は基本的にはStringBuilderに変換されるのです
ですので、演算子を積極的に使うことを推奨します
StringBuilderより演算子の方が読みやすいですよね

でもですね、newの数が無駄に多くなることがあります
その場合はインスタンス生成→解放の処理が無駄なので、素直にStringBuilderを使う方が良い場面もあります

演算子で良い場合、悪い場合を見ていきましょう!

バイトコードの参考
invokespecial:コンストラクタの呼び出し
invokevirtual:メソッドの呼び出し

ノーマル系

Test11

前置き
  • 1行で連結
  • String変数使用
解説

この1行で連結する形は、演算子でいいです

 

Test12

  • 1行で連結
  • 文字列使用

これも1行で連結する形ですね
しかも、#9のところを見れば分かるように、コンパイル時点でaiueokakikukekosasisusesoを準備してくれてます
なので演算子でいいです

 

Test13

  • 複数行で連結
  • String変数使用

Test11()を複数行で連結にした形です
複数行にするとnewを3個も呼び出すので、無駄になります

1行で連結するか、StringBuilderを使いましょう

 

Test14

  • 複数行で連結
  • 文字列使用

これもTest12()を複数行で連結にした形です
先ほどと同じように複数行にするとnewを3個も呼び出すので、無駄になります

1行で連結するか、StringBuilderを使いましょう

 

Test15

  • 1行で連結
  • String変数と文字列の混合使用

変数と文字列が混在していますが、1行連結ですので、newも1個ですね
演算子で問題ありません

 

ループ系

Test21

  • ループ使用
  • String変数使用
  • ループの【外】でString変数定義
  • 代入系

hogeをgoboへ100回代入してますけど、意味はないです
19:8:へgotoしているので、9:16:for文の処理です
特にnewしているわけでもないので、問題ないですね

演算子の使用でOKです

 

Test22

  • ループ使用
  • String変数使用
  • ループの【外】でString変数定義
  • 足していく系

Test21()を代入から常に連結に変えたものです

for文の処理はどこからどこまででしょうか?

36:でgotoしているので、9:33:for文の処理ですね

ということは、毎回newしちゃってます
無駄ですね…

連結しまくるなら、StringBuilderを使いましょう

 

Test23

  • ループ使用
  • String変数使用
  • ループの【中】でString変数定義
  • 代入系

Test21()の変数hogeの宣言位置がfor文の中に移動しました

しかし、newしていませんね

これは”aiueo”の値固定なので、11:で準備してくれてますね

演算子でいいでしょう

 

Test24

  • ループ使用
  • String変数使用
  • ループの【中】でString変数定義
  • 足していく系

Test22()の変数hogeの宣言位置がfor文の中に移動しましたが、形は殆ど変わりません

for文の中で毎回newしています

StringBuilderを使いましょう
StringBuilderの宣言はfor文の外でしましょう

 

Test25

  • ループ使用
  • 文字列使用
  • 代入系

Test21()での変数hogeが直接”aiueo”の文字列になりました
最適化内容はほぼ同じ

演算子の使用でOKです

 

Test26

  • ループ使用
  • 文字列使用
  • 足していく系

Test22()での変数hogeが直接”aiueo”の文字列になりました
最適化内容はほぼ同じで、毎回newしちゃってます
無駄ですね…

連結しまくるなら、StringBuilderを使いましょう

 

 

広告




 

javapで出力された内容を解説します(SampleBuilder編)

つぎに、StringBuilderを使った場合を見ます
バイトコードに展開されてもStringBuilderなので、ソースコードとバイトコードに大きな違いは出ませんが、一応見てみましょう

注目して欲しいのはこれ

  • newの数

 

参考
invokespecial:コンストラクタの呼び出し
invokevirtual:メソッドの呼び出し

ノーマル系

Test11

前置き
  • 1行で連結
  • String変数使用
解説

演算子の時よりも少し処理が多いですね
やっぱり1行で連結する形は、演算子がいいですね

 

Test12

  • 1行で連結
  • 文字列使用

これもTest11()と同じく、演算子の時よりも少し処理が多くなっていますね
やっぱり1行で連結する形は、演算子がいいですね

 

Test13

※無し

 

Test14

※無し

 

Test15

  • 1行で連結
  • String変数と文字列の混合使用

これもTest11()と同じく、演算子の時よりも少し処理が多くなっていますね
やっぱり1行で連結する形は、演算子がいいですね

 

ループ系

Test21

※無し

 

Test22

  • ループ使用
  • String変数使用
  • ループの【外】でString変数定義
  • 足していく系

これは演算子のバイトコードよりも処理が少なくなっています
しかし、処理の少なさよりも見てほしいのはnewの位置です

for文の処理はどこからどこまででしょうか?
28:でgotoしているので、14:25:for文の処理ですね

ということは、演算子の時と違ってnewは1回だけです

ループ文を使って連結しまくる処理の場合は、StringBuilderを使いましょう

 

Test23

※無し

 

Test24

  • ループ使用
  • String変数使用
  • ループの【中】でString変数定義
  • 足していく系

演算子の場合はfor文の中でnewを何度もしていましたが、StringBuilderを使った場合はnewは最初の1回だけです

ループで連結しまくるなら、StringBuilderを使いましょう

 

Test25

※無し

 

Test26

  • ループ使用
  • 文字列使用
  • 足していく系

演算子の場合はfor文の中でnewを何度もしていましたが、StringBuilderを使った場合はnewは最初の1回だけです

ループで連結しまくるなら、StringBuilderを使いましょう

 

 

広告




 

まとめ

まとめると、

  • Javaの文字連結は演算子を使い、コンパイル時の最適化に任せる方が良い
  • StringBuilderを使うと最適化してもらえないので、無駄な処理になることがある
  • とは言っても、演算子ではダメな場面もあるので、その時は自分でStringBuilderを使う
演算子とStringBuilderの結果まとめ
内容 演算子 StringBuilder
Test11
1行で連結
String変数使用


1行連結は演算子推奨!
Test12
1行で連結
文字列使用


1行連結は演算子推奨!
Test13
複数行で連結
String変数使用


複数行連結ならStringBuilder
もしくは1行連結に書き直すなら演算子が使える
Test14
複数行で連結
文字列使用


複数行連結ならStringBuilder
もしくは1行連結に書き直すなら演算子が使える
Test15
1行で連結
String変数と文字列の混合使用


1行連結は演算子推奨!
Test21
ループ使用
String変数使用
ループの【外】でString変数定義
代入系

ループ処理だけど同じ値を代入しているだけなので、何度もnewしないから演算子でOK
(現実的にはこんな処理を書くこと自体がおかしいけどねwww)
Test22
ループ使用
String変数使用
ループの【外】でString変数定義
足していく系

ループの中で毎回連結するならStringBuilderを使いましょう

Test23
ループ使用
String変数使用
ループの【中】でString変数定義
代入系

Test21と同じく、固定値を代入するだけなので演算子でOK
Test24
ループ使用
String変数使用
ループの【中】でString変数定義
足していく系

ループの中で毎回連結するならStringBuilderを使いましょう
Test25
ループ使用
文字列使用

代入系

Test21と同じく、固定値を代入するだけなので演算子でOK
Test26
ループ使用
文字列使用

足していく系

ループの中で毎回連結するならStringBuilderを使いましょう

 

さいごに、

実はですね、この記事では触れていないことがあります
それは処理速度です

今回の記事はバイトコードでのnewの位置や数に着目しましたが、処理速度も重要な要素です

newでインスタンス作成→解放が処理コストかかるはずなので、newの数が多いと処理時間もかかるはずですが、
とは言え、処理速度が気になる場合には今回のようにまずバイトコードを見た上で、処理時間を計測するとよりよりソースコードになるでしょう

 

今回見た内容はJava8の場合です
将来的にどうなるかは分かりません

どんな場合であっても、バイトコードが見られるならば、自分の目で確認すればいいだけのことです
javapを使ってバイトコードを見ることを覚えてください

 

 

個人的にJavaに対する感想は…なんか分かりづらいんですよねぇ~

演算子とStringBuilderを比較するというのが、なんか対称性がなくて、かなり変な感じがします(←この感覚分かる人には分かるでしょうけど、分からん人には分かんないですよねwww)

毎回毎回バイトコードを見るわけにはいかないですけど、ちょっとややこしいところはバイトコード見たり処理速度を見たりしましょう

 

あと、Javaだけじゃないですけど、プログラミングをする場合、コンパイラが最適化してくれる場合はその最適化が効くような書き方をすると良いですよ
C#はそういう思想だったはずですよね

 

今回はここまで!

 

 


 

プログラミング のレッスンに興味がある方、レッスン内容を聞いてみたい方、なんなりとお問い合わせください。
無料体験レッスンもありますのでお気軽にどうぞ!!!

 

 

広告
広告

プログラミングを楽しもう!

初心者向けのプログラミング教室やってます!
Skype or 対面で対応致します。
C言語、ExcelVBA、Scratch、Linux初歩の初歩といろいろなコースがあります。
※Rubyコース準備中!

小学生のお子さまにはScrachでプログラミングを楽しんで頂けます。

無料体験がありますので、是非お気軽にレッスンを受けてみてください。
プログラミングは楽しいですよ!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

ABOUTこの記事をかいた人

Windows/Mac/Linuxを使う現役システムエンジニア&プログラマ。オープン系・組み込み系・制御系・Webシステム系と幅広い案件に携わる。C言語やC#やJava等数多くのコンパイラ言語を経験したが、少し飽きてきたので、最近はRubyやPython、WordPressなどのWeb系を修得中。初心者向けのプログラミング教室も運営中。オンライン・対面・出張等でプログラミングをレッスンします。