super で親を呼ぶ
このレッスンで分かること
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 アプリの onCreate で super.onCreate(savedInstanceState) を書くのを見たことがある人もいると思いますが、その「お決まり」の正体もここで分かります。
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() は「親クラス Parent の greet() を呼ぶ」という意味なので、"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 アプリの Activity で super.onCreate(savedInstanceState) を呼ぶのも、これと同じ理屈です。フレームワーク側の初期化処理を呼んでからでないと、自分のロジックを書くと不整合が起こるので、お作法として最初に呼ぶ約束になっています。
this と super の使い分け
this と super は似て見えますが、指している対象が違います。this は「自分のインスタンス」、super は「親クラスから見たときの自分」です。次の表に違いをまとめます。
| 観点 | this | super |
|---|---|---|
| 指すもの | 自分のインスタンス | 親クラス側のメンバ |
| メソッド呼び出し | this.greet() (オーバーライド版が呼ばれる) | super.greet() (親版が呼ばれる) |
| コンストラクタ呼び出し | this(...) (同クラスの別コンストラクタ) | super(...) (親クラスのコンストラクタ) |
| 書ける場所 | インスタンスメソッド / コンストラクタ | 同上、ただし static メソッドでは使えない |
static なメソッドは特定のインスタンスに紐付かないので、this も super も使えません。コンパイラに non-static variable this cannot be referenced from a static context と叱られたら、たいてい static の中で this か super を書いてしまったケースです。
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クラス / メソッドを継承しようとする —StringMathなどのfinalクラスはextendsできません。エラーメッセージcannot inherit from finalが出たら、設計を見直して「継承」ではなく「コンポジション (フィールドとして持つ)」に切り替える方が筋がよいケースが多いです
もう 1 つ、ハマりやすいのが super と static の組み合わせです。static メソッドの中で super.foo() と書くと「static 文脈では super を参照できない」とエラーになります。super はあくまでインスタンスメソッドやコンストラクタの中だけで使える、と覚えておきましょう。
親のメソッドを
Child側で完全に置き換えたい (親を呼ばない) 場合は、super.greet()を 書かない のが正解です。「親を必ず呼ばないといけない」というルールはありません。呼ぶか呼ばないかは設計次第です。
super 呼び出しの順序を図で確認する
ここまでの説明を整理するために、Child.greet() を呼んだときの内部の流れを図にしておきます。順番を意識しておくと、後で Activity の onCreate のような複雑な継承を読むときに迷わなくなります。
ポイントになるのは、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" です。
手順は次のとおりです。
Parentクラスを用意し、public String greet()が"Hello"を返すようにするChildクラスでParentをextendsし、greet()をオーバーライドする- オーバーライドした
Child.greet()の中でsuper.greet() + " from child"をreturnする 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 つができることを確認しましょう。
- super の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. super とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
ChildクラスはParentをextendsで継承することChild.greet()はsuper.greet()を呼び、その結果に" from child"を連結して返すことSolution.describeChild()はnew Child().greet()の結果をreturnし、最終的に"Hello from child"を返すこと
入出力例
test-cases.txt
describeChild() → "Hello from child"