質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.80%

Swift  イニシャライザの自動的な継承について

受付中

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 619

score 138

https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘


上記のSwiftドキュメントページのイニシャライザについてなんですが、イニシャライザの継承の挙動について、
混乱しています。

ShoppingListItemクラスには自身のプロパティ(purchased)にデフォルト値があり、
指定イニシャライザを定義していないので、スーパークラスであるRecipeIngredientの
指定イニシャライザ・コンビニエンスイニシャライザを継承する、ということで
breakfastList[2]で考えてみます。

ShoppingListItem(name: "Eggs", quantity: 6)
が実行されると、ShoppingListItemが継承した、RecipeIngredientの指定イニシャライザが呼び出されるのだと思います。
継承したRecipeIngredientの指定イニシャライザは、

    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }


↑な訳ですが、継承しているので、上記のselfは、ShoppingListItemにとってのself、つまりShoppingListItem
上記のsuperはShoppingListItemにとってのsuper、つまりRecipeIngredient
というのが現在の認識なのですが、
そうすると、上記の

super.init(name:name)


は、RecipeIngredientのinit(name:name)、つまりRecipeIngredientのコンビニエンスイニシャライザが呼び出される、ということになり、そうするとquantityプロパティに1がセットされる訳ですが、
非常に複雑で訳がわからない(辻褄が合わない)し、結果を見ても違うようです。

ただ、これの一つ前の、RecipeIngredientクラスがFoodクラスのコンビニエンスイニシャライザを継承する説明では
やはり上記のように考えているようなので、ShoppingListItemのイニシャライザの継承はどう考えれば良いのかわからない状態です。

多分何か勘違いしているのだと思うのですが、どう考えれば良いでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

0

ShoppingListItem(name: "Eggs", quantity: 6)
が実行されると、ShoppingListItemが継承した、RecipeIngredientの指定イニシャライザが呼び出されるのだと思います。

ShoppingListItem のイニシャライザが定義されていない場合、サブクラスのイニシャライザを自動的に継承するので、その通りかと思います。

継承しているので、上記のselfは、ShoppingListItemにとってのself、つまりShoppingListItem
上記のsuperはShoppingListItemにとってのsuper、つまりRecipeIngredient
というのが現在の認識

この認識が違います。オーバーライドしているのではなく、継承したメソッドなので、RecipeIngredientがselfという認識になります。

普通に以下のようなコードを考えた際に、descriptionメソッドを継承したBicycleがdescriptionメソッドを読んだ場合、Vehicleで定義したdescriptionが呼ばれるのと同じです。

class Vehicle {
  func description() -> String {
    return "traveling at \(currentSpeed) miles per hour"
  }
}
class Bicycle: Vehicle {
    var hasBasket = false
}
let bicycle = Bicycle()
let desc = bicycle.description()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/30 14:36

    回答をいただきましてありがとうございます。
    お示し頂きましたコードなのですが、Vehicleクラスに初期化したプロパティを宣言するか、イニシャライザを
    定義するかしないとエラーが出るので、プロパティを宣言して、

    class Vehicle {
    var currentSpeed=0.0
    func description() -> String {
    return "traveling at \(currentSpeed) miles per hour"
    }
    }
    class Bicycle: Vehicle {
    var hasBasket = false
    }
    let bicycle = Bicycle()
    let desc = bicycle.description()
    print(desc)
    //traveling at 0.0 miles per hour
    上記のように表示されるのですが、この結果で、Bicycleのdescriptionではなく、
    Vehicleのdescriptionが呼び出された、と確認できますでしょうか?
    Bicycleのdescriptionが呼び出されても結局同じ表示になるような気がするのですが。

    キャンセル

0

>継承しているので、上記のselfは、ShoppingListItemにとってのself、つまりShoppingListItem
>上記のsuperはShoppingListItemにとってのsuper、つまり>RecipeIngredient
>というのが現在の認識なのですが、

前半はOKですが、後半は違っていると思います。

継承しているので、selfはご理解のとおりに変わります。

しかし、継承しても、superの型は変わりません。

ShoppingListItemとして生成したオブジェクトであっても、RecipeIngredient.init(name:)の中でのsuperは、Food型を指していて、RecipeIngredient型を指すように変わるわけではありません。
したがって、ここでのsuper.init(name:)は、Food.init(name:)を呼びます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/30 15:02

    回答を頂きましてありがとうございます。
    お示し頂いた考え方で辻褄が合うと思いますので、多分そうなんだと思いますが、
    「継承した場合selfが指す物は変わるが、superが指す物は変わらない」
    というのは、公式ドキュメントで説明されている箇所はありますでしょうか?

    キャンセル

  • 2020/01/30 16:27 編集

    探してみましたが、明示的にsuperが指す物はかわらない、という記述は見当たりませんでした。言い方が正しく無いのかもしれませんね。むしろselfが変わるという言い方が間違いかな・・・
    Inheritanceの章に、クラスはメソッド、プロパティ、サブスクリプトをオーバーライドできると書いてあります。親クラスをオーバーライドできるとは書いてありません。継承により、親クラスのコードであっても子クラスのメソッドを呼ぶようにできるのはオーバーライドによるものなので、オーバーライドできないsuperを自分のものに置き換えることはできない、と理解するべきなのかもしれませんね。
    実際にsuperを使うときは、親クラスで実装済みの機能を再利用するために呼びます。そのクラスの上位以外のsuperが呼ばれたら意図しない動作になるので使えません。

    キャンセル

0

難しく考えすぎです。

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity    // このselfはRecipeIngredientのインスタンスです
        super.init(name: name)      // このsuperはRecipeIngredientのスーパークラス。つまりFoodです。
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}


コメントの通りです。
これは継承などとは関係なく変わることはありません。

では、このselfShoppingListItemのイニシャライズ時はどうなるか。
ShoppingListItemのインスタンスはRecipeIngredientのインスタンスでもあります。継承関係にあるためShoppingListItemRecipeIngredientを内包しているからです。
ですので、このselfは初期化されようとするShoppingListItemのインスタンスのことです。

ShoppingListItemのインスタンスがRecipeIngredientのインスタンスでもあるということは

let a: RecipeIngredient = ShoppingListItem(name: "a")


が問題なくコンパイルできることから確認できます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る