19.0427

コンストラクタ(constructor)

コンストラクタ とは、
  設計図class を、実物化/インスタンス化 する特別な関数
です。

すでに、「19. class ⇒ instance」 のところで、
  デフォルト の、コンストラクタ
  Cat()
を、使いました。
ここでは、
  プロパティ を、含めた、書きかた
  省略できる場合の書き方
  val(プロパティ を、不変にする)で、定義する場合
  イニシャライザ で、インスタンス化時に、実行するコードを書く
などについて、考えてみましょう。

まずは、前回作ったようなコードで軽くおさらいしましょう。
このコードでは、
  13行目
  Cat1()
が、デフォルト の、コンストラクタ でした。
この場合は、
  コンストラクタ定義 は、不要
です。
ただ、コンストラクタ とは、何かを知るために、
省略されていないコードを、書いてみます。
これは、書かなくてもいい コンストラクタ を、書いた例です。
  2行目
を、見てください。
  constructor()
というコードが、ありますが、これが、
  コンストラクタ定義のコード
になります。
ただ、普通は、わざわざ書かないです。


1. コンストラクタ に、プロパティ定義 を、含めて書く
コンストラクタ定義 を含めて、クラスを書いてみましょう。
こんなコードになります。
このコードで、わざわざ、
  Cat1 ➡ Cat2
にしてあるのは、
  IntelliJ で、同じプロジェクト内に、
  2つのクラスを書くと、エラーになるからです。
というのは、
  ファイルが違っていても、
  同じプロジェクト内のコードは、同一のプログラムで、
  使われるという前提だから、2つも同じclass があるのは、
  やはり、普通に考えてもおかしいでしょう。
それで、わざと、クラス名 を、変更してあります、、ご了承願います。

このコードを見てみる前に、普通は、
  constructorキーワード
は、書かないので、そちらのコードに代えましょう

では、このコードを見ていきましょう。
このコードの利点は、
  ・1行で、プロパティ定義 までできてしまうこと(4行目)
  ・よって、クラスの 概要/イメージ/どんなクラスか が、一目で分かる
  ・プロパティの設定も、1行で、OK(12行目)
こんなところでしょう。
これは、先の、Cat1クラス と、
やっていることは、ほとんど同じですが、シンプルに書けました。


2. 省略して、コンストラクタ を使う。 val で、コンストラクタ を定義する
コンストラクタ の、引数 を、省略して、インスタンス化 することが、出来ます。
ただ、その場合、いろいろな省略に応じたアクション を、知っておかないと、
プログラマの、意図しない結果となることがあり得ます。
ここでは、いろいろな省略を見て、思い通りの結果になるようなコードを、考えてみます。
このコードを使って、考えてみましょう。

21行目 では、
  val buchi_1 = Cat3()
で、インスタンス化 しています。
その時、インスタンス変数 は、buchi_1 に、なります。
22行目では、そのインスタンス変数、buchi_1 を、使って、
メソッド、introduceMyself() を、呼んでいます。
実行結果を見てみると、
  吾輩の名前は、ぶちである。
  0歳に、なったところだ。
  です。
となっています。

これは、
  Cat3()
という、コンストラクタ実行コードは、
コンストラクタ定義
  (val name: String = "ぶち", var age: Int = 0, var color: String = "")
に、基づいているのです。
どうしてそうなるのかというと、
  コンストラクタ定義 を、したからには、
  その定義に従って、インスタンスを、組み立てなければならない

という事になっているからです。
Cat() というのは、単に、プロパティ が、省略されているに過ぎないのです。
ここで抑えるべきは、
  インスタンスは、コンストラクタ定義 に従って、組み立てられる
という事です。
コンストラクタ定義
  (val name: String = "ぶち", var age: Int = 0, var color: String = "")
に、基づいて考えれば、
  buchi_1.introduceMyself()
の、実行結果が、
  吾輩の名前は、ぶちである。
  0歳に、なったところだ。
  です。
となるのに、納得できるでしょう。


次に、
  26行目
を、見てみましょう。
  val buchi_2 = Cat3("buchi", 5)
で、インスタンス を、組み立て、変数buchi_2 に、代入しています。
右の図で、「やたら空白部分が多い」 のは、
  IntelliJ が、補助的に示している引数名 を、ペイントソフト(Jtrim) を使って、
  消しているからです。
  この透かしコード を、実際に書いたら、エラーですから。
  時短のためにやむなくこうしました、、ご了承ください。
25行目 を、見てください。
  // 順序を守れば、変数名 を、省略できる
と、ありますが、もう一度、コンストラクタ定義を見てみましょう。
  (val name: String = "ぶち", var age: Int = 0, var color: String = "")
でしたね。
という事は、
  (name, age, color)
の順で、定義されていることになります。
ですから、
  ("buchi", 5)
というのは、
  name, age
の順番を、守っているという事です。
  color に、関しては、デフォルトの、「""/空文字」 を、使って、省略
という事です。
もし、変数名 を、省略せずに書くとしたら、
  Cat3(name="buchi", age=5)
のように、書きます(val や var を書くと、エラーになります)。


それに対して、
  31,32行目
のコード
  val buchi_3 = Cat3(color="BlackAndWhite", age=4, name="ぶっち")
というのは、今度は、
  順番は正しくなくても、
  引数の値の、変数名(例えば、4 の、変数名は、age)を示して、
  例えば、age=4 のように書けば、OK
これは、プロパティ変数 が、とても多い時、便利なルールです。
なぜなら、いちいち順番を、覚えてられない可能性がありますね。


最後に、
  36行目
の、
  val buchi_4 = Cat3("ブッチー")
というコードについて考えてみます。
ここで、
  あれっ、プロパティ name に、関しては、
  val なので、
  "ぶち" 以外は、あり得ないのでは?
と、思われたかもしれませんが、
ここでの、
  val の意味は、
  ある特定のインスタンス に対して、
  name は、変更できない!

という事なのです。
ここのところは、かなりややこしいのですが、
この場合、
  インスタンスbuchi_4 の、name を、
  "ブッチー" にしたら、
  2度と、ブッチー以外は、Xです!
と言っているわけです、、ですが、
これは、ふつう正しい選択でしょう。
例えば、age/年齢 は、毎年変わっていくので、
val は、まずいですね。
でも、例えば、ペットの名前 を、変更する人は、
まあ、普通はないですから、val の、方が、正しいと言える
と、思われます。


3. コンストラクタに、値(初期値) は、指定しなくても、OK. ただし...
Kotlin は、原則として、
  変数 を、定義する時、値 を、設定する/決めておく
なのですが、
コンストラクタ定義 の場合、
  コンストラクタに、値(初期値) は、指定しなくても、OK.
  ただし、インスタンス化時(コンストラクタ を実行する時)には、
  必ず、初期化/値を決めること を、しなければならない!

という、ルールがあります。
これが、コード例です
3行目
を、見てください。
  class Cat4(val name: String, var age: Int)
    / この書き方なら、「{ }」 さえ、必要なく、たった1行で、class を、定義しています。
コンストラクタ は、
  (val name: String, var age: Int)
です。
そして、
  プロパティ変数 の、値 が、決められていません!
でも、コンストラクタ の場合、OK です。
ただし、必ず、インスタンス化 において、
  コンストラクタ の、プロパティ に、値 が決められていないときは、
  コンストラクタ を実行する時は、
  必ず、初期化しなければならない

という、ルールを、忘れては、いけません!
ですので、
  7行目 で、コメントアウト/実行しないコードにすること にしてある
  val tama = Cat4()
は、コンパイルエラー になります。
    / 実際には、10行目以降 に、書くコードです.
10行目の
  val tama = Cat4(name = "たま", age = 10)
のようにして、プロパティ変数 に、値 を、設定する必要があります。


4. コンストラクタ実行時 に、ついでに、実行コードするコード を書く
この意味を、もっと具体的に言うと、
  あるclassを、インスタンス化 する時に、
  インスタンス化以外に実行するコード を、書いておく
という意味になります。
具体的には、
  イニシャライザブロック
    init{ // コード }

の中に、
  プロパティ変数 の、初期化 が、
  終わった直後 に、実行するコード
を、書いておくのです。
こんなコードを、書いてみました
16行目の、
  val mike = Cat5()
が、実行されると、
  プロパティ変数、name と age が、設定された直後、
  init{ when文のコード }内のコード が、実行
    / 4~12行目
され、
  コンソールに、
  人間では、31~40
と、出力されています。


今回も、覚えることが、盛り沢山で、しかも、まだ、「セカンダリコンストラクタ」 も、
いずれは、学習すべきことです。
今回学習した コンストラクタ は、セカンダリコンストラクタ に、対して、
  プライマリコンストラクタ
と、呼ばれるものです。
メイン/1番目/主 の、コンストラクタ という意味です。
これだけのことを、一度に覚えるのは大変ですので、
一度読んで理解したら、コードを書くときに、参照して、
たくさんの例題コードを(理解しながら)書きながら、覚えた方が、
賢いやり方だと思います。



まとめ
コンストラクタ/constructor とは、
 設計図class を、インスタンス化 する、特別な関数です。
・ コンストラクタの書き方
  1. デフォルトコンストラクタ は、書かなくてもよいが、書くとしたら、
    () または、constructor() である。
  2. コンストラクタ内に、プロパティ(変数) 初期化 の、コードを含められる。
      例: (var name: String ="", var age: Int = 0) など
  3. コンストラクタ内 の、初期化コード を、「val」 で、定義できる。
     / ただし、ある特定なインスタンス に於いて、val で、定義された 値 は、変更できない。
     / これまたややこしいが、コンストラクタ内 の、val で、定義された 値でなくてもよい、、
     / あくまで、ある特定なインスタンス において、val で、定義された 値 が、変更できないのだ!
       / 具体的には、Cat3.ktファイル の、36~44行目 を、参照してください。
・ 省略して、コンストラクタ を、使う
  1. コンストラクタの引数無しで、実行できるが(例: Cat())、
    あくまで、コンストラクタ定義 に、基づいて、実行される。
    引数がなくても、コンストラクタ定義で、プロパティ が、設定してあれば、
    それに、基づいて、設定されるのである。
  2. コンストラクタの引数に、変数名 を、省略できるが、
    その時は、必ず、コンストラクタ内 の、プロパティ定義の順序 を、守る必要がある。
      / 例: Cat3.ktファイル の、25~28行目 を、見てください。
  3. コンストラクタの、引数に、プロパティ変数名を含めれば、
    コンストラクタ で、定義されているプロパティの順番 は、守らなくても、OKだ。
      / 例: Cat3.ktファイル の、30~34行目 を、見てください。
・ コンストラクタ内では、Kotlin の原則に反して、プロパティ に、初期値 を、設定しなくてもよいが、
 インスタンス化時に、必ず、初期化しておくこと!
・ イニシャライザブロック / init{ // コード } を、使えば、
 インスタンス化時に、プロパティの初期化の、すぐ後に、このコード内の、コードが実行できる。
・ この章で、扱った、コンストラクタ は、省略せずに言うと、プライマリコンストラクタ と言う。
   / セカンダリコンストラクタ は、のちに学習すべきことです。