概要
**class定義の前に"final"ってついているけど、どうしてこういう風に書いてるの?**って聞かれたので、自分でも少し曖昧だった部分を調べてまとめました。
finalってなに
- クラスにfinal修飾子をつけると、継承されるのを禁止できます
- メソッド、プロパティ、サブスクリプトにfinal修飾子をつけると、サブクラスでオーバーライド(上書き)されるのを禁止できます
finalの意味の通り、クラスであれば、このクラス以降、継承されることがない最後のクラスというのを示します。
class Animal { }
final class Dog: Animal { }
class Chihuahua: Dog { } // Dogのサブクラスは作成できない
finalをオーバーライドしようとするとどうなるか
サブクラスでfinalのついたメソッド、プロパティ、サブスクリプトをオーバライドしようとすると、コンパイルエラーになります。
// プロパティ、メソッド、サブスクリプトにfinalがついているクラス
class SomeClass {
final var someProperty: Int = 0
final func someMethod() {
}
final subscript(index: Int) -> Int {
return index * 2
}
}
// SomeClassを継承したサブクラス
class SomeSubClass: SomeClass {
// プロパティがオーバーライドできないのでコンパイルエラー
// error: var overrides a 'final' var
override var someProperty: Int {
get {
return super.someProperty + 1
}
set {
super.someProperty = newValue
}
}
// メソッドがオーバーライドできないのでコンパイルエラー
// error: instance method overrides a 'final' instance method
override func someMethod() {
}
// サブスクリプトがオーバーライドできないのでコンパ入りエラー
// error: subscript overrides a 'final' subscript
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
また同様に、finalのついたクラスを継承したサブクラスを作ろうとすると、継承自体ができなくなり、コンパイルエラーとなります。
final class SomeClass {
}
// SomeClassを継承したサブクラス
class SomeSubClass: SomeClass {
// コンパイルエラー error: inheritance from a final class
}
どういうときにfinalをつけるか
継承の必要性がないときにはfinalをつけるのが良いと思います。
逆にサブクラスを作成する必要がでてきたらfinalを外します。
finalをつけるメリット
- 動的ディスパッチ(後述)の可能性を減らし、プログラムの実行時のパフォーマンスが改善できる
- サブクラスを経由してセキュリティ対策等必ず実行したい処理を回避されることを防ぐ
- サブクラスを作成することにより、設計が崩れることを防ぐ
- 継承・オーバーライドされることがないものとしてプログラムの理解に役立つ
動的ディスパッチとは
動的ディスパッチ(Dynamic Dispatch)は動的結合や遅延結合とも呼ばれ、プログラムの実行時に利用するプロパティやインスタンスを決定することを意味します。
動的ディスパッチが行われるサンプルコードがこちらです。
// キャラクターはattack(攻撃)メソッドを持つ
protocol Character {
func attack()
}
// ゴクウ
class Goku: Character {
func attack() {
print("かめはめは!")
}
}
// ゴハン(ゴクウの子クラス)
class Gohan: Goku {
override func attack() {
print("ませんこう!")
}
}
//isGokuはランダムでtrueもしくはfalseになる
let isGoku: Bool = arc4random_uniform(2) == 0
let character = isGoku ? Goku() : Gohan()
character.attack()
このプログラムでは、isGoku
の値がtrueかどうかによって、キャラクターが決定し、その攻撃メソッド(attack)が変わります。
また、isGoku
の値は実行時にランダムで決定するため、character.attack()
でGoku
クラスのattack()
が実行されるのかGohan
クラスのattack()
が実行されるのかはコンパイラは判断できません。
実行時にオブジェクトによって振る舞いが決定する、**ポリモーフィズム(Polymorphism: 多態性)**の例にもなっています。
動的ディスパッチはパフォーマンスを犠牲にするため、実行速度が求められるようなプログラムでは適しません。final
でサブクラスがないことを明示し、private
などでアクセスレベルを限定することで、コンパイル時に参照するメソッドやプロパティを限定し、動的ディスパッチの可能性を減らすことでよりパフォーマンスの良いプログラムを作成できます。