プログラミングいちねんせい

プログラミング初心者が優雅にもがく軌跡です

文字列を連結するなら演算子ではなくStringBuilderが推奨されるらしいです

文字の連結といえば+演算子を使ったおなじみの式でしたが、それよりもStringBuilderが推奨されているという噂を聞きつけました。というわけで今回もいろいろ見ていきます。

StringBuilderとは

文字の可変シーケンスです。このクラスは、StringBufferと互換性があるAPIを提供しますが、同期化は保証されません。このクラスは、文字列バッファが単一のスレッド(一般的なケース)により使用されていた場合のStringBufferの簡単な代替として使用されるよう設計されています。このクラスは、ほとんどの実装で高速に実行されるので、可能な場合は、StringBufferよりも優先して使用することをお薦めします。

今のところは文字列の連結は+演算子StringBuilderStringBufferの3つがありStringBufferよりStringBuilderのほうが処理速度が速いので優先して使ったほうがいいみたいですね。

StringBuilderの基本的なオペレーションには、appendメソッドおよびinsertメソッドがあり、これらのメソッドはどんな種類のデータも受け取ることができるようにオーバーロードされています。メソッドはそれぞれ与えられたデータを効率的に文字列に変換し、文字列中の文字を文字列ビルダーに追加または挿入します。appendメソッドは常に、ビルダーの末尾に与えられた文字を追加し、insertメソッドは指定された位置に文字を追加します。

たとえば、zを、現在「start」を含む文字列ビルダー・オブジェクトと見なす場合、z.append(“le”)は文字列ビルダーの内容が「startle」になるように変更するのに対して、z.insert(4, “le”)というメソッド呼出しは文字列ビルダーの内容が「starlet」になるように作用します。

一般に、sbがStringBuilderのインスタンスを参照している場合、sb.append(x)はsb.insert(sb.length(), x)と同じ結果になります。

appendメソッドとinsertメソッドが代表メソッドでappendメソッドが末尾に文字列を追加しinsertメソッドが引数で指定した位置に文字列を追加するみたいです。

プラス演算子との違い

可変であるStringBuilderに対して固定であるStringは文字列を追加するたびにnewされるらしいです。

この場合3回もnewされるのでその分のメモリを消費します。それが原因で+演算子での文字列結合は遅いみたいです。そう言われるとStringBuilderを使わない手はないですね。

ここで初心者が一瞬混乱する例を見てみましょう。

この結果はどうなると思いますか?■■■■■■■■■■が出力されると思いきや実は■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■こう表示されるんです。が55個並んでいます。いったいどういうことでしょうか?原因は3行目のStringBuilder sb = new StringBuilder();です。for文の外側でsb変数を定義しているのでfor文の実行内容が変数sbの末尾に追加されていくわけです。ではfor文の中に入れてみましょう。

これで結果は■■■■■■■■■■となります。考えてみればなんでもないのですが2つ目のコードの変数sbfor文の条件がtrueであれば、その都度newされるので変数sbの中身がnullになります。気をつけましょう。

メソッドを試す

appendメソッドで文字列を末尾にできます。これは簡単ですね。

insertメソッドです。引数で指定した箇所に文字列を挿入します。

deleteCharAtメソッドです。引数で指定した箇所の文字列を削除します。

便利なメソッドばかりなので文字列を扱うときはStringBuilderは便利ですね。

用語

  • シーケンス
    • 連続、順序
  • API
    • システムやプログラムに用意された拡張機能
  • バッファ
    • データを一時的に記憶する場所
  • スレッド
    • 処理をするCPUの単位

あとがき

簡単なプログラムのときはStringで書くと思いますがStringBuilderには慣れておきたいです。

初心者殺しのfor文のネストを解説します

みなさんはfor文と仲良くやっていますか?私は昨日まで喧嘩ばかりしていました。あっちに行ったりこっちに行ったりと縦横無尽に駆け回るfor文がなにを考えているのかわかりませんでした。しかし、ふと目に止まったJAVAの質問の解を自分の手でやってみようと試行錯誤していたときに一筋の光が差し込みfor文がこう言いました。

「友達になろう」と。

for文の基本

まずはfor文の基本をおさらいしましょう。for文は初期化、条件、変化、実行内容の4つで構成されています。

基本

  • 初期化
    • 変数の値を決める場所です。プログラミング初心者は初期化と聞くと0になると考えがちですが間違いです。実際はどんな値で開始するかという意味です。for文が開始されると最初に一度だけ実行されます。変数の型を宣言するのを忘れないでください
  • 条件
    • この条件がfalseになると終了します。つまり条件が満たされている限り実行内容が繰り返されます
  • 変化
    • 条件に書いた変数がどう変化するのかを指定します
  • 実行内容
    • 計算したり文字を出力したりといった内容を書きます

実演

  • int
    • int型を宣言しています
  • i = 0
    • 変数i0を代入します
  • i < 10
    • i10未満ならtrueです。つまりi9までのあいだは実行内容を繰り返します
  • i++
    • iが1つ増えます
  • System.out.println("Hello!")
    • Hello!という文字を出力します

動き

実演をもとに見ていきましょう。for文が開始されると最初に動くのは初期化です。変数iの値は0ですね。for文はi0だと確認してから条件に移ります。条件はi < 10です。今の時点では0 < 10と同じです。日本語に直すと「0は10より小さい」と言えるので条件はtrueですね。条件がtrueということが確認できたので実行内容の部分が動きます。

System.out.println("Hello!")が実行されると次は変化に移ります。変化はi++です。今の時点では0++なので1になりました。この1は実行の変数に代入するために条件に移ります。今の時点では1 < 10です。trueですね。というわけでまた実行内容に移ります。

この流れが何度も続くと条件のなかが10 < 10になります。これはtrueでしょうか?falseでしょうか?falseですね。これでfor文は終了です。お疲れ様でした。

流れをリスト化すると以下になります。

  1. for文開始
  2. 初期化
  3. 条件
  4. 実行内容
  5. 変化
  6. 条件(以下略)
  7. 条件がfalseならfor文終了

実は簡単だったfor文のネスト

for文の基本をおさえたところで次はネストです。私はfor文のネストをなんとなくわかったつもりになっていましたが実際に書いてみると上のfor文と下のforがどういう風に絡むのかという点がわかっていませんでした。

では、九九の計算プログラムを紐解きながらネストの動きを見ていきましょう。

九九を表示するプログラム

ここから先は「上のfor文」ではなく「外側のfor文」、「下のfor文」ではなく「内側のfor文」という表現になります。では、3行目からです。int x = 1;から実行されます。次にx < 10;です。今回はtrueなので実行内容に移ります。初心者は実行内容がどこなのか真っ白になるところですが下の図をご覧ください。

f:id:javacurry:20170620195251p:plain

for文を拡大して色分けしました。これを見るとわかるように外側のfor文の実行内容というのは内側である青のfor文なんです。内側のfor文(青)の初期値のint y = 1が実行されて条件に移ります。y < 10;なので代入して1 < 10です。trueなので実行内容に移ります。System.out.print(x * y)です。xは外側のfor文の変数でしたね。今の時点ではx=1です。なので、ここは1 * 1が実行されて結果は1です。画面に1が表示されて次の行に移ります。System.out.print(" ")は半角の空白が表示されます。

ここまで実行されたら次は変化に移ります。y++なのでyが1つ増えて2と変化して次は条件に移ります。y < 10なので2 < 10となりtrueなので、また実行内容へと移ります。System.out.print(x * y)です。1 * 2なので結果は2となり画面へ表示され次の行の半角の空白も表示されて、また変化に移り以降は繰り返しです。このような動きでyの値が9までいくと画面には1 2 3 4 5 6 7 8 9と表示されますね。このあとなにが起きるかを見てみましょう。

y++y10となり条件に移ります。10 < 10falseなので内側のfor文(青)の一番下のSystem.out.println()が実行されて改行されます。これで内側のfor文(青)は終了するので外側のfor文(赤)に戻ります。ここからは繰り返しです。2 * 1から始まり2 * 9までいけば改行で3 * 1が実行されます。では実際の実行結果を見てみましょう。

これがfor文のネストを利用した九九のプログラムの動きです。紐解くと簡単ですね。これを応用して冒頭ではなしたとある質問を作ってみました。その結果がこちらです。

これはなにをやっているかというとfor文を使っての数を1行ごとに増やして三角形を表現したんです。1つずつ増える形と1つずつ減る形を作り上下を合わせたものを表示しました。以下がそのプログラムです。

ここまで読んだみなさんなら、もう解説しなくとも紐解けますね。

あとがき

プログラミング初心者にとってfor文のネストの動きをすぐに理解するのは難しいです。見るだけではなく、まずは自分の手で試行錯誤するのをおすすめします。今回作った三角形を左側に反転させたものを作りたくなり四角形に挑戦したのですが半角の空白を連続で表示することができずに断念しました。いつかfor文で簡単な絵を描いてみたいです。

サブクラスのフィールドの定義の方法で失敗したのでシェアします

サブクラスのフィールドにアクセスしたらnull0しか返ってこず足が止まりました。これはプログラミング初心者あるあるかもしれないので警告しておくとフィールドはオーバーライドできません。そう言われるとそんなことわかってると反論があるかもしれませんが、私たち初心者はなんとなくサブクラスで定義した値が通ると感じませんか?実は通りません。そこで、みんな大好きteratailで解決したので復習しておきたいと思います。

スーパークラスとサブクラスにおけるフィールドの定義の方法

同名フィールドはスーパークラスに定義するべし

スーパークラスMonsterとサブクラスのGoblinです。初心者が見たら違和感はないと思います。では、ここでHeroクラスのattackメソッドを呼び出します。

7行目のmonster.nameと9行目のmonster.hpの中はどうなっていると思いますか?なんと、ここで冒頭ではなしたnull0が登場するんです。私はてっきりゴブリン10にアクセスしていると思っていました。頭の中が真っ白です。しかし、私の知識では検索しようにも適切なワードが出てきませんでした。真っ白のなかterateilで得られた答えは同じ意味を持ったフィールドは基底クラスにのみ宣言することだったんです。

基幹クラスに宣言とは

サブクラスであるGoblinにはフィールドを定義せずにスーパークラスであるMonsterに定義するということでした。Monsterクラスの6~11行目のコンストラクタを定義しておきGoblinクラスの4~6行目のコンストラクタのsuperで、さきほど定義しておいたMonsterクラスのコンストラクタを呼び出すという方法です。目からうろこですね!

Monsterクラスの7~8行目のassertは指定した条件がtrueでない場合にエラーになるという仕組みみたいです。初めて見ました。

結果

見事に各フィールドにアクセスできました!試しに残りのHPも表示してみました。他にもgetNameの定義とサブクラスでのオーバーライドなどの情報をいただきました。もやもやしていた部分が今回ですっきりしました。

あとがき

とある、気になった質問を試していたらfor文のネストもすっきりしたので次回に書いてみます。