Swift & Objective-C

オーバーロード vs オーバーライド:Swiftで見分ける

オーバーロードとオーバーライド。ここまで名前が似ているのはなぜだろうと思う2つの概念です。

読了 5 分
オーバーロード vs オーバーライド:Swiftで見分けるのカバー画像

オーバーロードとオーバーライド。ここまで名前が似ているのはなぜだろうと思う2つの概念です。

試験や面接で頻出するテーマですが、混乱しやすい大きな理由は名前にあります。動作の仕組みはまったく異なります。前者は「同じ名前の関数を複数作ること」、後者は「継承した関数を再定義すること」です。

決まるタイミングも異なります。一方はコンパイル時、もう一方は実行時です。この違いは、静的ディスパッチと動的ディスパッチという性能の話にもつながります。

この記事では、Swiftの例を使って両者を明確に区別します。

要点をまとめます。

  1. オーバーロード:同じ名前で、異なるパラメータの関数を複数定義。継承とは無関係
  2. オーバーライド:親のメソッドを子が再定義。継承が前提
  3. オーバーロードはコンパイル時に、オーバーライドは実行時に呼び出す関数が決まる
  4. オーバーライドはポリモーフィズムの中核となる仕組み

オーバーロード:同じ名前を複数定義

オーバーロードとは、名前は同じで、パラメータの構成(数・型・ラベル)が異なる関数を複数定義することです。

func area(radius: Double) -> Double { .pi * radius * radius }
func area(width: Double, height: Double) -> Double { width * height }
func area(side: Double) -> Double { side * side }

コンパイラは呼び出し側から渡された引数を見て、どの関数かを決定します。area(radius: 3)と書いた瞬間、1つ目の関数に確定します。実行前にはすでに決まっています。

オーバーロードのなかったC言語を思い出すと、その便利さがわかります。絶対値関数は型ごとにabs、labs、fabsと名前が異なりました。オーバーロードは、概念が同じなら名前も同じにする仕組みです。

Swiftでは、print(:)やmin(:_:)など、標準ライブラリのいたるところでオーバーロードが使われています。演算子のオーバーロードにより、+ 1つで整数の加算、文字列の連結、配列の結合も処理できます。

注意点が1つあります。戻り値の型だけが異なるオーバーロードは避けるのが無難です。Swiftでは構文上可能な場合もありますが、呼び出し側で型を明示しないと曖昧性エラーになり、読者も混乱します。


オーバーライド:継承したものを再定義

オーバーライドとは、子クラスが親クラスから継承したメソッドを、同じシグネチャで再定義することです。継承関係が前提になります。

class Animal {
    func speak() { print("...") }
}
class Dog: Animal {
    override func speak() { print("ワンワン") }
}
class Cat: Animal {
    override func speak() { print("ニャー") }
}

重要なのはこの場面です。

let animals: [Animal] = [Dog(), Cat()]
for animal in animals {
    animal.speak() // ワンワン、ニャー
}

変数の型はAnimalですが、実行されるのは実際のインスタンスのメソッドです。どのspeakを呼ぶかは、実行時に実際のオブジェクトを見て決まります。これが動的ディスパッチであり、ポリモーフィズムを動かす仕組みです。

Swiftがoverrideキーワードを必須にしている点も重要です。親メソッドと同じシグネチャを誤って作ったり、タイプミスで再定義に失敗したりするミスを、コンパイラが検出してくれます。逆に、親側でfinalを付ければ再定義を禁止できます。

変数の型がAnimalでも、実行されるのは実際のインスタンスのメソッドです
変数の型がAnimalでも、実行されるのは実際のインスタンスのメソッドです

表で一気に比較

項目 オーバーロード オーバーライド
定義 同じ名前、異なるパラメータ 同じシグネチャを再定義
継承 不要 必須
決定時点 コンパイル時(静的) 実行時(動的)
目的 名前の一貫性、APIの利便性 ポリモーフィズム、振る舞いの置き換え
Swiftキーワード なし override(必須)、finalで禁止

決定時点の違いは性能にも関係します。コンパイル時に確定する呼び出し(静的ディスパッチ)は直接ジャンプできますが、実行時に決まる呼び出し(動的ディスパッチ)は実際の型のメソッドテーブルを経由する必要があります。Swiftではfinalやprivateを付けると、コンパイラが動的ディスパッチを静的ディスパッチに変換して最適化できる余地が生まれます。

オーバーロードはコンパイル時、オーバーライドは実行時に決まる
オーバーロードはコンパイル時、オーバーライドは実行時に決まる

混同しない覚え方

英単語の意味どおりに覚えましょう。

Overload = 過積載。同じ名前の上に関数を複数「載せる」イメージです。1つの名前に複数の機能。

Override = 無視して踏み越える。親の定義を「上書き」するイメージです。定義は1つで、内容を置き換えます。

面接では、次のように答え始めるとよいでしょう。

「オーバーロードは、パラメータが異なる同名の関数を複数用意することで、コンパイル時に決まります。オーバーライドは、継承したメソッドを再定義することで、実行時に実際の型を基準に決まります。ポリモーフィズムを生み出すのはオーバーライドです。」


まとめ

  • オーバーロード:同じ名前で異なるパラメータ構成の関数を複数定義。継承は不要
  • オーバーライド:親メソッドを子が同じシグネチャで再定義。継承が必須
  • オーバーロードはコンパイル時(静的)、オーバーライドは実行時(動的)に呼び出し先が決まる
  • ポリモーフィズムの中核となる仕組みは、オーバーライドと動的ディスパッチです
  • Swiftはoverrideキーワードを必須にしてミスを防ぎ、finalで再定義を禁止できます
  • 覚え方:Overloadは名前に過積載、Overrideは定義を上書き

あわせて読みたい