super で親を呼ぶ

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • super() で親クラスのコンストラクタを呼ぶ ─ 子の constructor の 必ず最初 に書く
  • super.method() で親のメソッドを呼ぶ ─ override したメソッド内で親実装を再利用するとき
  • super.field で親フィールドにアクセス ─ 同名フィールドがあるときの曖昧さ回避
  • super を書かなくても、デフォルトで super() (引数なし) が暗黙呼び出しされる

super で親を呼ぶ とは

子クラスから親クラスのメソッドやコンストラクタを呼ぶときに使う super キーワードの意味と使い方を、greet メソッドのオーバーライド例を通して身につけよう。

super キーワードとは何か

Java の継承を学び始めると、まず必ず出会うのが extends で親クラスから機能を引き継ぐ書き方です。子クラスは親のフィールドやメソッドをそのまま使えますが、ときには「親のメソッドを override (オーバーライド) しつつ、親の処理も呼びたい」という場面が出てきます。たとえば親の greet()"Hello" を返すなら、子は "Hello from child" のように、親の挨拶に一言だけ足したいことがあります。

このときに使うのが super キーワードです。super は「親クラス側の自分」を指す参照で、super.method() と書けば親のメソッドを呼べますし、コンストラクタの中で super(...) と書けば親のコンストラクタを呼べます。this が「自分自身」を指すのに対して、super は「親クラスとしての自分」を指すと覚えると分かりやすいです。

Java の言語仕様では、子クラスのインスタンスは「親クラスの部分」と「子クラス独自の部分」を内側に持つ二段重ねの構造になっています。super はその「親の段」にアクセスするためのキーワード、と理解すると腑に落ちます。

この章では、super の典型的な使い道である「親メソッドの呼び出し」と「親コンストラクタの呼び出し」を、ハマりやすいポイントと一緒に解説していきます。Android アプリの onCreatesuper.onCreate(savedInstanceState) を書くのを見たことがある人もいると思いますが、その「お決まり」の正体もここで分かります。

diagram (will load when visible)

super.method() で親のメソッドを呼ぶ

まずは一番よく使う super.method() から見ていきましょう。子クラスが親と同じ名前のメソッドを定義した時点で、その親のメソッドは子のスコープからは「隠されて」しまいます。普通に greet() と書くと、子の greet() が呼ばれるので、何も工夫しないと無限再帰になる恐れがあります。

Java

class Parent { public String greet() { return "Hello"; } } class Child extends Parent { @Override public String greet() { // ここで greet() と書くと自分自身を呼んでしまう return super.greet() + " from child"; } }

super.greet() は「親クラス Parentgreet() を呼ぶ」という意味なので、"Hello" が返ってきます。それに " from child" を連結して return すれば "Hello from child" という文字列になります。@Override アノテーションは必須ではありませんが、書いておくと「これは親メソッドの上書きですよ」とコンパイラに伝わり、シグネチャがズレているとエラーで教えてくれるので必ず付けましょう。

super は「親をひとつ上にたどる」ためのキーワードで、super.super.greet() のように二段以上さかのぼることは できません。途中の親をすっ飛ばして祖父母クラスのメソッドを直接呼ぶ手段は Java には用意されていません。

親メソッドを呼ぶときは、引数も親のシグネチャに合わせて渡します。super.calc(10, 20) のように普通のメソッド呼び出しと同じ書き方です。戻り値も普通に変数に受けられますし、そのまま return してもかまいません。「super. を付けるかどうかだけが違う」と覚えておけば十分です。

super(...) で親のコンストラクタを呼ぶ

もう 1 つの使い方が、コンストラクタの中で書く super(...) です。これは「親クラスのコンストラクタをここで呼ぶ」という意味で、子クラスのコンストラクタの 一番最初の行 に書くというルールがあります。

Java

class Animal { String name; public Animal(String name) { this.name = name; } } class Dog extends Animal { int age; public Dog(String name, int age) { super(name); // Animal(name) を呼んで name を初期化 this.age = age; } }

super(name) を呼ぶことで、Animal 側の this.name = name; が実行され、その後で Dog 側の this.age = age; が走ります。super(...) を書き忘れると、コンパイラは「引数なしの super()」を勝手に挿入しようとしますが、親に引数なしコンストラクタがない場合は constructor Animal in class Animal cannot be applied to given types のようなエラーになります。親に引数ありコンストラクタしかないときは、必ず明示的に super(...) を呼ぶ必要があります。

super(...)最初の行 に書く」というルールは厳格で、if 文や try ブロックの中に入れることもできません。this(...) で別のコンストラクタを呼ぶ場合も同様で、どちらか 片方を 1 行目だけ に書けます。両方を同時に書くことはできません。

Android アプリの Activitysuper.onCreate(savedInstanceState) を呼ぶのも、これと同じ理屈です。フレームワーク側の初期化処理を呼んでからでないと、自分のロジックを書くと不整合が起こるので、お作法として最初に呼ぶ約束になっています。

thissuper の使い分け

thissuper は似て見えますが、指している対象が違います。this は「自分のインスタンス」、super は「親クラスから見たときの自分」です。次の表に違いをまとめます。

観点thissuper
指すもの自分のインスタンス親クラス側のメンバ
メソッド呼び出しthis.greet() (オーバーライド版が呼ばれる)super.greet() (親版が呼ばれる)
コンストラクタ呼び出しthis(...) (同クラスの別コンストラクタ)super(...) (親クラスのコンストラクタ)
書ける場所インスタンスメソッド / コンストラクタ同上、ただし static メソッドでは使えない

static なメソッドは特定のインスタンスに紐付かないので、thissuper も使えません。コンパイラに non-static variable this cannot be referenced from a static context と叱られたら、たいてい static の中で thissuper を書いてしまったケースです。

final クラスは継承できない

ここで 1 つ覚えておきたいのが、final 修飾子の話です。final class String のように final が付いたクラスは、extends で継承することができません。継承できなければ super も登場しようがありません。String Integer Boolean などの基本的なラッパー型はすべて final で、設計上わざと拡張できないようにしてあります。

Java

// これはコンパイルエラーになる class MyString extends String { // cannot inherit from final java.lang.String }

似た話で、final メソッドは子クラスでオーバーライドできません。親が public final String greet() と書いていたら、Child 側で @Override しようとしてもエラーになります。「ここは絶対に上書きしないでね」という設計者の意思表示なので、無理にオーバーライドしようとせず、別名のメソッドを用意するのが正解です。

final は「これ以上いじらせない」の合図です。クラス・メソッド・変数のどれにも付けられますが、意味はそれぞれ違うので使うときは目的を意識しましょう。継承の文脈では「拡張禁止」と読み替えればおおむね正しいです。

よくある間違い

ここまでの内容を踏まえて、super まわりで初学者がよくハマるポイントを 3 つ整理します。事前に知っておくとデバッグの時間がぐっと縮みます。

  • super(...) を 2 行目以降に書いてしまうsuper(...)this(...)コンストラクタの 1 行目 に限ります。前にログ出力やバリデーションを入れたくなりますが、それをするとコンパイルエラーです。どうしても前処理したいときは static メソッドに切り出して、super(prepare(arg)) のように引数の中で呼びましょう
  • オーバーライドした子で super なしの自己呼び出しをしてしまうChild.greet() の中で greet() と書くと、this.greet() 扱いで自分自身が呼ばれ、無限再帰で StackOverflowError になります。親を呼びたいなら必ず super.greet() と書くこと
  • final クラス / メソッドを継承しようとするString Math などの final クラスは extends できません。エラーメッセージ cannot inherit from final が出たら、設計を見直して「継承」ではなく「コンポジション (フィールドとして持つ)」に切り替える方が筋がよいケースが多いです

もう 1 つ、ハマりやすいのが superstatic の組み合わせです。static メソッドの中で super.foo() と書くと「static 文脈では super を参照できない」とエラーになります。super はあくまでインスタンスメソッドやコンストラクタの中だけで使える、と覚えておきましょう。

親のメソッドを Child 側で完全に置き換えたい (親を呼ばない) 場合は、super.greet()書かない のが正解です。「親を必ず呼ばないといけない」というルールはありません。呼ぶか呼ばないかは設計次第です。

super 呼び出しの順序を図で確認する

ここまでの説明を整理するために、Child.greet() を呼んだときの内部の流れを図にしておきます。順番を意識しておくと、後で ActivityonCreate のような複雑な継承を読むときに迷わなくなります。

diagram (will load when visible)

ポイントになるのは、Child.greet() の中で super.greet() を呼んだ瞬間に、制御が Parent.greet() に移って "Hello" が返ってくるところです。そこに子側で " from child" を連結して、最終的に呼び出し側へ "Hello from child" が返ります。super は「親に処理を委譲して、戻ってきた結果を加工する」というパターンの典型例です。

実務で遭遇するパターン

中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。

  • フレームワーク拡張super(parent) で親クラスの初期化を引き継ぎつつ自分の状態を追加
  • テンプレートメソッドsuper.beforeRun() を呼んでから自分の処理を足す、ベースクラス + 拡張パターン
  • 例外チェーンsuper("message", cause) で原因例外を引き継ぐカスタム例外
  • Spring の @Configuration 継承super.dataSource() で親の Bean を取り出して上書き

コードレビューで指摘されがちなポイント

PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。

  • super() を 2 行目以降に書いている ─ コンパイルエラー。constructor の最初の文でなければいけない
  • super.method() を呼び忘れて親の初期化を飛ばす ─ ベースクラスのフックが効かなくなるバグ
  • 親が引数ありコンストラクタしか持たないのに super() を書いていない ─ 暗黙呼び出しが失敗してコンパイルエラー
  • this()super() を同じ constructor で並べる ─ どちらも最初に書く必要があるので不可

パフォーマンス考慮事項

  • super() のオーバーヘッドはゼロ ─ JIT が完全にインライン化
  • 親 constructor の重さ ─ 親が DB 接続などを開くと、子インスタンス生成のたびに発生
  • 深い継承の constructor チェーン ─ 5 階層あれば 5 回の constructor 呼び出しになる
  • フィールド初期化順序 ─ 親フィールド → 子フィールドの順。親初期化中に子フィールドは null
この章のポイント

ここまでの要点 super() は親 constructor 呼び出しで constructor の最初の文。super.method() は override 中に親実装を再利用。

やってみよう

それでは今回の課題に挑戦してみましょう。今回作るのは Solution.describeChild() というメソッドで、内部で Parent を継承した Child をインスタンス化し、その greet() の結果を return します。最終的に返ってくる文字列は "Hello from child" です。

手順は次のとおりです。

  1. Parent クラスを用意し、public String greet()"Hello" を返すようにする
  2. Child クラスで Parentextends し、greet() をオーバーライドする
  3. オーバーライドした Child.greet() の中で super.greet() + " from child"return する
  4. Solution.describeChild()new Child().greet() を呼んで、その結果をそのまま return する

テストでは describeChild() の戻り値が文字列 "Hello from child" になっているかを確認します。スペースや大文字小文字を間違えると一致しないので、super.greet() + " from child"" from child" の先頭スペースを忘れずに付けてください。

慣れてきたら、Parent 側を final class Parent にしてみるとどうなるか、super(...) をコンストラクタの 2 行目に書くとどんなエラーが出るか、なども実験してみてください。失敗するパターンを自分の手で見ておくと、いざ業務コードで詰まったときに「あ、これあの本で見たやつだ」と気づけるようになります。次のレッスンでは、もう一歩踏み込んでポリモーフィズムを使った設計を見ていきます。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は メソッドのオーバーライド で、メソッドのオーバーライド を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. super の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. super とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. Child クラスは Parentextends で継承すること
  2. Child.greet()super.greet() を呼び、その結果に " from child" を連結して返すこと
  3. Solution.describeChild()new Child().greet() の結果を return し、最終的に "Hello from child" を返すこと

入出力例

test-cases.txt

describeChild()"Hello from child"

ヒント

main.java
main.java
学習モード

メモ

super で親を呼ぶ

⌘S で保存