継承
オブジェクト指向プログラミングでは継承と呼ばれる機能が存在します。継承とはあるクラスのプロパティとメソッドを引き継ぐ機能のことです。例えば、次のようなPersonクラスがあったとします。
このクラスを継承した場合、Personクラスのイニシャライザを除いて、全てのプロパティとメソッド、デイニシャライザを引き継ぐことになります。では、以降でSwiftでどのように継承するのかをみていきましょう。
継承の定義
継承では、継承元のクラスをスーパークラスもしくは親クラスと呼びます。また、スーパークラスを継承したクラスのことをサブクラスもしくは子クラスと呼びます。Swiftではあるクラスを継承したクラスを定義したい時、次のような構文で定義できます。
サブクラス名 : スーパークラス名{ //サブクラスで新たにプロパティとメソッドを定義する }
サブクラスはスーパークラスの全てのプロパティとメソッドを継承しますので、サブクラスで新しく追加したいプロパティとメソッドのみを定義するだけで済みます。
次の例では、Personクラスを継承したStudentクラスを定義しています。
StudentクラスはPersonクラスを継承しているだけで、新しくプロパティ、メソッドを追加していません。このStudentクラスをインスタンス化し、student変数に代入しています。その後、printSelfIntroductionメソッドを呼び出しています。StudentクラスはPersonクラスを継承していますので、Personクラスの全てのプロパティとメソッドを引き継いでいます。このため、studentクラスのインスタンスからprintSelfIntroductionメソッドが呼び出せるのです。
もちろん、サブクラスでプロパティやメソッドを新たに追加することも可能です。次の例はStudentクラスに出席番号を表すnoプロパティとprintNoを追加した例です。
オーバーライド
サブクラスはスーパークラスから継承したプロパティやメソッドをカスタマイズすること可能です。この機能は「オーバーライド(上書き)」と呼ばれています。オーバーライドは次のような構文で実現できます。
サブクラス名 : スーパークラス名{ override func 上書きしたいメソッド名(...){ } }
オーバーライドしたいメソッドにoverrideキーワードをつけます。そして、funcキーワードに続けて、スーパークラスで定義されている上書きしたいメソッド名を記述します。
次の例ではPersonクラスのprintSelfIntroductionメソッドをStudentクラスで上書きする例です。
printSelfIntroductionメソッドをStudentクラスでオーバーライドした後、StudentクラスのインスタンスからprintSelfIntroductionメソッドを呼び出すと、オーバーライドされたprintSelfIntroductionメソッドが呼びされてることが確認できます。このようにoverrideキーワードを利用して同名の関数の処理を上書きすることが可能です。
スーパークラスのメソッドを呼び出す
さて、先ほどのStudentクラスのprintSelfIntroductionメソッドですが、「println("私の名前は\(self.name)です。年齢は\(self.age)歳です。")」の処理はスーパークラスで定義されているprintSelfIntroductionメソッドと同じです。プログラムでは同じ処理を複数の箇所に記述することは管理コストを考えると好ましくありません。このため、StudentクラスのprintSelfIntroductionメソッドの「println("私の名前は\(self.name)です。年齢は\(self.age)歳です。")」の部分の処理はスーパークラスのprintSelfIntroductionメソッドを呼び出すようにします。スーパークラスのメソッドを呼び出したい時はsuperキーワードを利用します。superキーワードは「インスタンスのスーパークラス」を示すキーワードです。次のような構文でスーパークラスのメソッドを呼び出すことができます。
サブクラス名 : スーパークラス名{ func メソッド名(...){ super.メソッド名(...) } }
次の例ではStudentクラスのprintSelfIntroductionメソッドの「println("私の名前は\(self.name)です。年齢は\(self.age)歳です。")」の部分の処理をスーパークラスのprintSelfIntroductionメソッドを呼び出すように変更しています。
出力結果から、superキーワードを利用して、スーパークラスのprintSelfIntroductionメソッドを呼び出すことが出来ていることが確認できます。また、このようにしておくことで、仮にPersonクラスのprintSelfIntroductionメソッドで処理の変更があったとしても、Studentクラスは一切の影響なく、変更を反映させることができます。
クラスの継承と初期化処理(イニシャライザ)
イニシャライザとはクラスからインスタンスを生成する時に、プロパティ等の初期値を決定する処理でした。イニシャライザの基本的な定義、利用方法については初期化処理(イニシャライザ)を参照してください。イニシャライザの詳細を突き詰めていくと、指定イニシャライザとコンビニエンスイニシャライザの2つがあります。始めに両者の違いを確認し、そのあと、継承と初期化処理の関係を確認していきます。
指定イニシャライザとコンビニエンスイニシャライザ
クラスによっては複数のイニシャライザをもつものがあります。このうち、インスタンスを初期化するために必須の働きをするイニシャライザは指定イニシャライザと呼ばれます。一方、利用者側が簡単にインスタンスを生成できるように提供されるのがコンビニエンスイニシャライザです。指定イニシャライザはクラスにとって必須のイニシャライザですが、コンビニエンスイニシャライザはオプションであり、定義されていなくても問題ありません。指定イニシャライザを定義する構文を次のようになります。
init(パラメータ,..) { 文 }
また、コンビニエンスイニシャライザはconvenienceキーワードをinitの前に置くことで定義できます。次のような構文になります。
convenience init(パラメータ,..) { 文 }
イニシャライザチェイン
Swiftでは指定イニシャライザとコンビニエンスイニシャライザの関係を簡単にするために次のルールを適用しています。
- ルール1
指定イニシャライザは直接のスーパークラスの指定イニシャライザを呼ぶ
- ルール2
コンビニエンスイニシャライザは同一のクラスの別のイニシャライザを呼び出す
- ルール3
コンビニエンスイニシャライザは最終的に指定イニシャライザを呼び出す
では、このルールに従ってPersonクラスのイニシャライザを定義していきます。次の例ではPersonクラスにコンビニエンスイニシャライザを追加した例です。
コンビニエンスイニシャライザ自身はプロパティの初期化を行わず、指定イニシャライザに初期化処理を委譲しています。このようにコンビニエンスイニシャライザはあくまで補助的な役割で、初期化処理は指定イニシャライザで行います。また、次の例ではPersonクラスを継承したStudentクラスでイニシャライザを定義した例です。
継承したクラスでは直接のスーパークラスの指定イニシャライザを呼ぶ必要があります。StudentクラスのスーパークラスはPersonクラスですので、Personクラスの指定イニシャライザを呼ぶ必要があります。Personクラスの指定イニシャライザはinit()とinit(name:String,age:Int)があります。ここでは、Personクラスの指定イニシャライザinit(name:String,age:Int)を呼び出して、初期化を行っています。
2段階初期化
Swiftでのクラスの初期化は2段階で行われます。第1段階ではクラスがもつストアドプロパティを初期化します。すべてのプロパティの初期化がすんだら、第2段階として新しいインスタンスを利用するためのカスタマイズ処理を行います。2段階に初期化処理を分けることで、初期化が完了していないプロパティにアクセスすることを防ぐことができます。2段階初期化が正しく行われているかはコンパイラがチェックしてくれます。コンパイラは次のようなチェックを行ってくれます。
- チェック1
指定イニシャライザはスーパークラスのイニシャライザを呼び出す前に自身のプロパティがすべて初期化されているかチェックします。
- チェック2
継承元のクラスのプロパティに値を代入する前にスーパークラスのイニシャライザをコールしているかチェックします。
先にスーパークラスのイニシャライザをコールしないと、代入した値がスーパークラスのイニシャライザで上書きされてしまうからです。
- チェック3
コンビニエンスイニシャライザは値を設定する前に、別のイニシャライザに処理を委譲しているかチェックします。
値を設定しても指定イニシャライザによって値が上書きされてしまうからです。
- チェック4
イニシャライザの第1段階が完了するまで、インスタンスメソッドが呼ばれていないかチェックします。
2段階初期化の処理の流れ
2段階初期化の処理の流れは次のようになります。
第1段階
- イニシャライザが呼び出される
- メモリが確保されるがまだ初期化されていない
- 指定イニシャライザにより自クラスのプロパティが初期化される
- 指定イニシャライザはスーパークラスのイニシャライザに処理を委譲する
- この処理は継承チェーンの一番上にたどり着くまで繰り返される。
- 継承チェーンの一番上まで初期化が終われば、すべてのプロパティが初期化されたことが保証される
第2段階
- オプションのカスタマイズ処理やインスタンスメソッドを呼び出して何らかの処理をする
イニシャライザのオーバーライド
サブクラスはスーパークラスのイニシャライザは継承しません。スーパークラスと同じイニシャライザをサブクラスで提供したい場合、サブクラスでスーパークラスのイニシャライザをオーバーライドする必要があります。overrideキーワードを指定し、かつ、スーパークラスの指定イニシャライザと同一のイニシャライザをサブクラスでかけば、指定イニシャライザをオーバーライドできます。次の例ではPersonクラスを継承したStudentクラスでinit(name:Strig,age:Int)をオーバーライドしています。
このようにoverrideキーワードをつけることでスーパークラスのイニシャライザをオーバーライドして、スーパークラスのイニシャライザのカスタマイズを行うことが可能です。
継承による自動イニシャライザ
上述の通り、クラスを継承してもスーパークラスのイニシャライザはサブクラスに継承されません。しかし、次の条件に合致した場合、自動でサブクラスのイニシャライザを定義してくれます。
- ルール1
サブクラスが指定イニシャライザを定義していなかった場合、スーパークラスの指定イニシャライザをサブクラスに自動で定義します
- ルール2
サブクラスがスーパークラスのすべての指定イニシャライザを実装していた場合、自動でスーパークラスのコンビニエンスコンストラクタを継承します
必須イニシャライザ
サブクラスにオーバーライドすることを強制するイニシャライザのことを必須イニシャライザと呼びます。必須イニシャライザはキーワードrequiredを付けて、次のような構文で定義します。
class スーパークラス名 { required init(パラメータ,...) { //初期化処理 } } class サブクラス名 : スーパークラス名 { required init(パラメータ,...) { //初期化処理 } }
次の例ではPersonクラスのinit(name:Strig,age:Int)イニシャライザにrequiredキーワードを付けています。
このようにrequiredキーワードをつけた場合、Personクラスを継承したサブクラスはinit(name:Strig,age:Int)イニシャライザをオーバーライドする必要があります。さらに、サブクラスでオーバーライドする場合、requiredキーワードをつける必要があります。このため、Personクラスを継承したStudentではinit(name:Strig,age:Int)イニシャライザをオーバーライドし、かつ、requiredキーワードをつけています。
オーバーライドの禁止
外部に提供するプログラムでは、場合によっては継承をさせたくなかったり、オーバーライドをさせたくない場合もでてきます。もし、実装したクラスやメソッドを継承させたくない場合にはキーワードfinalを利用します。
メソッドのオーバーライド禁止
メソッドのオーバーライドを禁止したい場合、funcキーワードの前にfinalをつけます。 クラス名{ final func メソッド名(...){ super.メソッド名(...) } }
次の例ではPersonクラスのprintSelfIntroductionメソッドにfinal句をつけて、メソッドのオーバーライドを抑止しています。
この場合、StudentクラスがprintSelfIntroductionメソッドをオーバーライドしているため、コンパイルエラーが発生します。このようにメソッドにfinalキーワードをつけることでオーバーライドを抑止することができます。
継承の禁止
継承を禁止させたい場合、クラス名の前にfinalをつけます。
final クラス名{ func メソッド名(...){ super.メソッド名(...) } }
次の例ではPersonクラスにfinal句をつけて、サブクラス化を抑止しています。
この場合、StudentクラスがPersonクラスを継承しているため、コンパイルエラーが発生します。このようにクラスにfinalキーワードをつけることで継承を抑止することができます。