- JAVA
Java superとは?super()とthisの違い・親クラス呼び出し
Javaのsuperとは、子クラスの中から親クラス(スーパークラス)のコンストラクタ・メソッド・フィールドを指すためのキーワードです。 super() は親クラスのコンストラクタを呼び、super.メソッド名() は親クラスのメソッドを、super.フィールド名 は親クラスのフィールドを参照します。対になる this は「自分自身のインスタンス」を指します。
この記事では、super と this の5つの使い方を1枚の表にまとめ、コンストラクタが呼ばれる順番を実行結果で確かめます。さらに、親クラスに引数なしコンストラクタが無いときのコンパイルエラー、super() を先頭以外に書いたときのエラーを実際のメッセージつきで再現し、暗黙の super() が挿入されるルールを整理します。Java 25で正式機能になった「super()の前に文を書ける」変更(Flexible Constructor Bodies)についても、OpenJDK 21.0.10 と OpenJDK 25.0.4.1 の両方で同じコードを動かして違いを確認しました。
superとthisの早見表
まず5種類の書き方を比べます。カッコが付く super() this() はコンストラクタの呼び出し、ドットが付く super. this. はメンバーへのアクセスです。
| 書き方 | 意味 | 書ける場所 | 主な用途 |
|---|---|---|---|
super(引数) |
親クラスのコンストラクタを呼ぶ | コンストラクタの中だけ。Java 24までは先頭の1文 | 親クラスのフィールドを初期化する |
super.メソッド() |
親クラスのメソッドを呼ぶ | インスタンスメソッド・コンストラクタの中 | オーバーライドしたメソッドから元の処理を呼ぶ |
super.フィールド |
親クラスのフィールドを参照する | インスタンスメソッド・コンストラクタの中 | 子クラスに同名のフィールドがあるときに親側を指す |
this(引数) |
同じクラスの別のコンストラクタを呼ぶ | コンストラクタの中だけ。Java 24までは先頭の1文 | 引数の少ないコンストラクタから多いものへ処理をまとめる |
this.フィールド / this.メソッド() |
自分自身のインスタンスのメンバーを指す | インスタンスメソッド・コンストラクタの中 | 引数と同名のフィールドを区別する |
あわせて、よく聞かれるルールをまとめます。
| ルール | 内容 |
|---|---|
| super()を書かなかったら | コンパイラが引数なしの super(); を先頭に自動で入れる |
| super()とthis()を両方書けるか | 1つのコンストラクタに書けるのはどちらか1つだけ |
| staticメソッドで使えるか | super も this も使えない(インスタンスが無いため) |
| super.super.メソッド()は書けるか | 書けない。直接指せるのは1つ上の親クラスまで |
| super()の前に文を書けるか | Java 24まではコンパイルエラー。Java 25からは条件つきで書ける |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java superとは?super()とthisの違い・親クラス呼び出し」
super()で親クラスのコンストラクタを呼ぶ
コンストラクタが呼ばれる順番を確かめる
super() は、子クラスのコンストラクタから親クラスのコンストラクタを呼び出す書き方です。インスタンスは「親クラスの部分 → 子クラスの部分」の順に作られます。
以下のサンプルは、1つのファイルに複数のクラスを書いています。javac Order1.java でコンパイルし、java Order1 で実行してください。
class Parent {
Parent() {
System.out.println("1. Parent のコンストラクタ");
}
}
class Child extends Parent {
Child() {
super(); // 親クラスのコンストラクタを呼ぶ
System.out.println("2. Child のコンストラクタ");
}
}
public class Order1 {
public static void main(String[] args) {
new Child();
}
}
実行結果(OpenJDK 21.0.10):
1. Parent のコンストラクタ
2. Child のコンストラクタ
new Child() と書いただけですが、先にParentのコンストラクタが動いています。子クラスのインスタンスは親クラスのフィールドも持っているので、親クラスの初期化を先に済ませてから子クラスの初期化に進む、という順番になっています。
引数つきのsuper()で親のフィールドを初期化する
親クラスのコンストラクタが引数を受け取る場合は、super(引数) で値を渡します。
class Item {
private final String name;
private final int price;
Item(String name, int price) {
this.name = name;
this.price = price;
System.out.println("Item(" + name + ", " + price + ")");
}
String getName() { return name; }
int getPrice() { return price; }
}
class Book extends Item {
private final String author;
Book(String name, int price, String author) {
super(name, price); // 親の (String, int) コンストラクタへ
this.author = author;
System.out.println("Book(" + author + ")");
}
String getAuthor() { return author; }
}
public class Args {
public static void main(String[] args) {
Book b = new Book("Java入門", 2800, "山田");
System.out.println(b.getName() + " / " + b.getPrice() + "円 / " + b.getAuthor());
}
}
実行結果:
Item(Java入門, 2800)
Book(山田)
Java入門 / 2800円 / 山田
name と price はItemのprivateフィールドなので、Bookから直接代入することはできません。親クラスのprivateフィールドは、super(引数) を通して親クラス自身に初期化してもらいます。 どのコンストラクタが呼ばれるかは、メソッドのオーバーロードと同じく、引数の数と型で決まります。
暗黙のsuper()が挿入されるルール
書かなくても親のコンストラクタは呼ばれる
super() は省略できます。コンストラクタの中で super(...) も this(...) も呼んでいなければ、コンパイラが引数なしの super(); を先頭に自動で入れます。
class Parent {
Parent() {
System.out.println("1. Parent のコンストラクタ");
}
}
class Child extends Parent {
Child() {
// super(); を書いていない
System.out.println("2. Child のコンストラクタ");
}
}
class GrandChild extends Child {
// コンストラクタを1つも書いていない
}
public class Implicit {
public static void main(String[] args) {
System.out.println("-- new Child() --");
new Child();
System.out.println("-- new GrandChild() --");
new GrandChild();
}
}
実行結果:
-- new Child() --
1. Parent のコンストラクタ
2. Child のコンストラクタ
-- new GrandChild() --
1. Parent のコンストラクタ
2. Child のコンストラクタ
super(); を書いていないChildでも、Parentのコンストラクタが先に動きました。GrandChildにはコンストラクタを1つも書いていませんが、コンパイラが GrandChild() { super(); } という引数なしのコンストラクタ(デフォルトコンストラクタ)を自動で用意するため、Childのコンストラクタ、さらにその先のParentのコンストラクタへと順に呼び出しがつながります。
コンパイラが補うものの一覧
| 自分で書いたコード | コンパイラが補うもの |
|---|---|
| コンストラクタを1つも書いていない | 引数なしのコンストラクタ(中身は super();) |
コンストラクタはあるが super(...) も this(...) も書いていない |
そのコンストラクタの先頭に super(); |
先頭に this(...) を書いている |
何も補わない(呼び出し先のコンストラクタ側で super() が呼ばれる) |
super(...) を書いている |
何も補わない |
extends を書いていないクラス |
親クラスは Object。暗黙の super(); は Object() を呼ぶ |
補われるのは必ず「引数なし」の super(); です。 ここが次のコンパイルエラーの原因になります。
親に引数なしコンストラクタが無いときのコンパイルエラー
エラーを再現する
親クラスに引数ありのコンストラクタだけを書くと、デフォルトコンストラクタは自動で作られなくなります。この状態で子クラスが暗黙の super(); に頼ると、コンパイルエラーになります。
class Item {
String name;
Item(String name) {
this.name = name;
}
}
class Book extends Item {
Book() {
System.out.println("Book を作りました");
}
}
public class NoDefault {
public static void main(String[] args) {
new Book();
}
}
実行結果(OpenJDK 21.0.10、コンパイルエラー):
NoDefault.java:8: error: constructor Item in class Item cannot be applied to given types;
Book() {
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
1 error
メッセージを読み解くと次のとおりです。
| メッセージの部分 | 意味 |
|---|---|
constructor Item in class Item cannot be applied to given types |
Itemクラスのコンストラクタを、渡された引数では呼び出せない |
required: String |
Itemのコンストラクタに必要な引数はString |
found: no arguments |
実際には引数なしで呼ぼうとした |
reason: actual and formal argument lists differ in length |
引数の数が合っていない |
エラーの行は Book() { を指していて、super という文字はどこにも出てきません。自分では書いていない暗黙の super(); が原因なので、初めて見ると戸惑いやすいエラーです。
子クラスにコンストラクタを1つも書かなかった場合も、同じエラーになります。
class Item {
String name;
Item(String name) {
this.name = name;
}
}
class Book extends Item {
}
public class NoDefault2 {
public static void main(String[] args) {
new Book();
}
}
実行結果(コンパイルエラー):
NoDefault2.java:7: error: constructor Item in class Item cannot be applied to given types;
class Book extends Item {
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
1 error
今度はエラーの位置が class Book extends Item { の行になりました。自動で作られるデフォルトコンストラクタの中の super(); が呼び出し先を見つけられないためです。
直し方
class Item {
String name;
Item(String name) {
this.name = name;
}
}
class Book extends Item {
Book() {
super("名称未設定"); // 直し方1: 親の引数ありコンストラクタを明示して呼ぶ
System.out.println("Book を作りました: " + name);
}
Book(String name) {
super(name);
System.out.println("Book を作りました: " + name);
}
}
public class NoDefaultFixed {
public static void main(String[] args) {
new Book();
new Book("Java入門");
}
}
実行結果:
Book を作りました: 名称未設定
Book を作りました: Java入門
| 直し方 | 書き方 | 向いている場面 |
|---|---|---|
子クラスで super(引数) を明示する |
super(name); |
親クラスが値を必須にしているとき。基本はこちら |
| 親クラスに引数なしコンストラクタを足す | Item() { this("名称未設定"); } |
値なしでも作れてよいとき、親クラスを自分で変更できるとき |
親クラスが「nameは必須」という意図で引数ありコンストラクタだけを用意しているなら、子クラス側で super(引数) を書くのが素直な直し方です。
super()を先頭以外に書いたときのエラー
Java 21で再現する
super() の前に文を書くと、Java 21ではコンパイルエラーになります。
class Item {
String name;
Item(String name) {
this.name = name;
}
}
class Book extends Item {
Book(String name) {
System.out.println("名前を確認します: " + name);
super(name);
}
}
public class NotFirst {
public static void main(String[] args) {
Book b = new Book("Java入門");
System.out.println("作成: " + b.name);
}
}
実行結果(OpenJDK 21.0.10、コンパイルエラー):
NotFirst.java:10: error: call to super must be first statement in constructor
super(name);
^
1 error
「superの呼び出しはコンストラクタの最初の文でなければなりません」というメッセージです。this(...) を先頭以外に書いた場合も同じ種類のエラーになります。
super()とthis()を両方書いたとき
class Item {
Item(String name) { }
}
class Book extends Item {
Book() {
this("無題");
super("無題");
}
Book(String name) {
super(name);
}
}
public class Both {
public static void main(String[] args) { new Book(); }
}
実行結果(コンパイルエラー):
| JDK | メッセージ |
|---|---|
| OpenJDK 21.0.10 | Both.java:7: error: call to super must be first statement in constructor |
| OpenJDK 25.0.4.1 | Both.java:7: error: redundant explicit constructor invocation |
どのバージョンでも、1つのコンストラクタに書けるコンストラクタ呼び出しは super(...) か this(...) のどちらか1つだけです。 this(...) で呼んだ先のコンストラクタが super(...) を呼ぶので、親クラスの初期化は必ず1回だけ行われます。
Java 21で引数をチェックしてからsuper()に渡す方法
「親クラスに渡す前に引数をチェックしたい」という場面は多くあります。Java 21では、staticメソッドを super(...) の引数の中で呼ぶことで対応できます。
class Item {
final String name;
Item(String name) {
System.out.println("Item のコンストラクタ: " + name);
this.name = name;
}
}
class Book extends Item {
Book(String name) {
super(check(name)); // static メソッドを引数の中で呼ぶ
System.out.println("Book のコンストラクタ");
}
private static String check(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name は必須です");
}
return name.trim();
}
}
public class Validate21 {
public static void main(String[] args) {
new Book(" Java入門 ");
try {
new Book(" ");
} catch (IllegalArgumentException e) {
System.out.println("例外: " + e.getMessage());
}
}
}
実行結果(OpenJDK 21.0.10):
Item のコンストラクタ: Java入門
Book のコンストラクタ
例外: name は必須です
2回目の new Book(" ") では、Itemのコンストラクタが動く前に例外が投げられています。check をstaticにしているのは、super() が終わるまではインスタンスメソッドを呼べないためです。
Java 25でsuper()の前に文を書けるようになった
バージョン別の状況
「super()は先頭の1文」という制限は、Flexible Constructor Bodies(柔軟なコンストラクタ本体) という機能で緩和されました。OpenJDKのJEP 513のページを開いて確認した経緯は次のとおりです。
| JDK | JEP | 状態 | super()の前の文 |
|---|---|---|---|
| 21以前 | ― | ― | 書けない(コンパイルエラー) |
| 22 | JEP 447 | プレビュー(1回目) | --enable-preview を付けたときだけ書ける |
| 23 | JEP 482 | プレビュー(2回目) | 同上 |
| 24 | JEP 492 | プレビュー(3回目) | 同上 |
| 25 | JEP 513 | 正式機能 | オプションなしで書ける |
JDK 22〜24でのプレビュー動作は手元では未実行です(JDK 21と25で確認しました)。長期サポート版でいえば、Java 21では書けず、Java 25から書けると覚えておくとわかりやすいでしょう。
同じコードをJDK 25で動かす
先ほどJava 21でエラーになった NotFirst.java を、OpenJDK 25.0.4.1でそのまま実行した結果です。
名前を確認します: Java入門
作成: Java入門
エラーにならず、super(name) の前の println が先に実行されました。引数のチェックも、staticメソッドに切り出さずにそのまま書けます。
class Item {
final String name;
Item(String name) {
System.out.println("Item のコンストラクタ: " + name);
this.name = name;
}
}
class Book extends Item {
Book(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name は必須です");
}
super(name.trim());
System.out.println("Book のコンストラクタ");
}
}
public class Validate {
public static void main(String[] args) {
new Book(" Java入門 ");
try {
new Book(" ");
} catch (IllegalArgumentException e) {
System.out.println("例外: " + e.getMessage());
}
}
}
| JDK | 結果 |
|---|---|
| OpenJDK 21.0.10 | Validate.java:13: error: call to super must be first statement in constructor |
| OpenJDK 25.0.4.1 | 下の実行結果のとおり動作 |
実行結果(OpenJDK 25.0.4.1):
Item のコンストラクタ: Java入門
Book のコンストラクタ
例外: name は必須です
super()の前に書けること・書けないこと
Java 25でも、何でも書けるわけではありません。JEP 513では、super() の前の部分では作成途中のインスタンスを使えないと定められています(例外は、自クラスのフィールドへの代入です)。実際に確かめます。
class Parent {
String label = "親";
Parent() { }
void hello() { }
}
class Child extends Parent {
int size;
Child() {
System.out.println(this.size); // 自分のフィールドを読む
hello(); // インスタンスメソッドを呼ぶ
System.out.println(super.label); // 親のフィールドを読む
super();
}
}
public class Early25 {
public static void main(String[] args) { new Child(); }
}
実行結果(OpenJDK 25.0.4.1、コンパイルエラー):
Early25.java:9: error: cannot reference this before supertype constructor has been called
System.out.println(this.size); // 自分のフィールドを読む
^
Early25.java:10: error: cannot reference hello() before supertype constructor has been called
hello(); // インスタンスメソッドを呼ぶ
^
Early25.java:11: error: cannot reference super before supertype constructor has been called
System.out.println(super.label); // 親のフィールドを読む
^
3 errors
| super()の前に書くこと | Java 25 | 確認方法 |
|---|---|---|
| 引数のチェックと例外のスロー | 書ける | Validate.javaで実行 |
ローカル変数の宣言や計算、println |
書ける | NotFirst.javaで実行 |
自クラスのフィールドへの代入(this.name = name;) |
書ける(初期化子を持たないフィールド) | 次のPitfall25.javaで実行 |
| 自クラスのフィールドを読む | コンパイルエラー | Early25.javaで実行 |
| インスタンスメソッドを呼ぶ | コンパイルエラー | Early25.javaで実行 |
super.フィールド super.メソッド() を使う |
コンパイルエラー | Early25.javaで実行 |
親のコンストラクタからオーバーライドしたメソッドが呼ばれる場合
コンストラクタの順番に関連して、知っておくと役立つ動きがあります。親クラスのコンストラクタが、子クラスでオーバーライドされたメソッドを呼ぶと、子クラスのフィールドがまだ初期化されていない状態で動きます。
class Parent {
Parent() {
show(); // 子クラスでオーバーライドされたメソッドが呼ばれる
}
void show() { System.out.println("Parent.show"); }
}
class Child extends Parent {
private final String name;
private int size = 10;
Child(String name) {
super();
this.name = name;
}
@Override
void show() { System.out.println("Child.show: name=" + name + ", size=" + size); }
}
public class Pitfall {
public static void main(String[] args) {
Child c = new Child("A");
c.show();
}
}
実行結果(OpenJDK 21.0.10):
Child.show: name=null, size=0
Child.show: name=A, size=10
1行目は親のコンストラクタの中から呼ばれた show() で、name はnull、size は0になっています。size = 10 という初期化も、this.name = name という代入も、super() が終わった後に行われるためです。コンストラクタの中では、オーバーライドできるメソッドを呼ばないようにすると、この動きを避けられます。
Java 25では、super() の前にフィールドへ代入しておく書き方も選べます。
class Parent {
Parent() {
show();
}
void show() { System.out.println("Parent.show"); }
}
class Child extends Parent {
private final String name;
Child(String name) {
this.name = name; // Java 25: super() の前に自分のフィールドへ代入できる
super();
}
@Override
void show() { System.out.println("Child.show: name=" + name); }
}
public class Pitfall25 {
public static void main(String[] args) {
new Child("A");
}
}
実行結果(OpenJDK 25.0.4.1):
Child.show: name=A
同じファイルをOpenJDK 21.0.10でコンパイルすると call to super must be first statement in constructor のエラーになります。
初期化の順番を1つのコードで確かめる
staticの初期化、フィールドの初期化、初期化ブロック、コンストラクタ本体がどの順で動くかを、番号を付けて表示します。
class Parent {
static { System.out.println("1. Parent の static 初期化"); }
String p = log("3. Parent のフィールド初期化");
{ System.out.println("4. Parent の初期化ブロック"); }
Parent() {
System.out.println("5. Parent のコンストラクタ本体");
}
static String log(String s) { System.out.println(s); return s; }
}
class Child extends Parent {
static { System.out.println("2. Child の static 初期化"); }
String c = log("6. Child のフィールド初期化");
{ System.out.println("7. Child の初期化ブロック"); }
Child() {
super();
System.out.println("8. Child のコンストラクタ本体");
}
}
public class InitOrder {
public static void main(String[] args) {
new Child();
System.out.println("-- 2回目 --");
new Child();
}
}
実行結果(OpenJDK 21.0.10):
1. Parent の static 初期化
2. Child の static 初期化
3. Parent のフィールド初期化
4. Parent の初期化ブロック
5. Parent のコンストラクタ本体
6. Child のフィールド初期化
7. Child の初期化ブロック
8. Child のコンストラクタ本体
-- 2回目 --
3. Parent のフィールド初期化
4. Parent の初期化ブロック
5. Parent のコンストラクタ本体
6. Child のフィールド初期化
7. Child の初期化ブロック
8. Child のコンストラクタ本体
| 順番 | 動くもの | 回数 |
|---|---|---|
| 1 | 親クラスのstatic初期化 | クラスが最初に使われるときに1回だけ |
| 2 | 子クラスのstatic初期化 | 同上 |
| 3 | 親クラスのフィールド初期化・初期化ブロック(書いた順) | new のたび |
| 4 | 親クラスのコンストラクタ本体 | new のたび |
| 5 | 子クラスのフィールド初期化・初期化ブロック(書いた順) | new のたび |
| 6 | 子クラスのコンストラクタ本体(super() の後ろの部分) |
new のたび |
子クラスのフィールド初期化は、super() が終わった直後に行われます。 前の節で size が0だったのは、この順番によるものです。
super.メソッドで親クラスのメソッドを呼ぶ
オーバーライドしたメソッドから元の処理を呼ぶ
子クラスでメソッドをオーバーライド(上書き)すると、通常は子クラス側のメソッドが呼ばれます。親クラスの処理を活かしつつ、子クラスの処理を足したいときに super.メソッド名() を使います。
class Member {
String name = "佐藤";
String describe() {
return "会員: " + name;
}
}
class PremiumMember extends Member {
int points = 1200;
@Override
String describe() {
// 親の describe() の結果に、子クラスの情報を足す
return super.describe() + " / ポイント: " + points;
}
}
public class Method {
public static void main(String[] args) {
System.out.println(new Member().describe());
System.out.println(new PremiumMember().describe());
}
}
実行結果:
会員: 佐藤
会員: 佐藤 / ポイント: 1200
「会員: 佐藤」の部分を子クラスにもう一度書かずに済んでいます。toString() や equals() をオーバーライドするときに super.toString() を呼ぶのも同じ使い方です。super() と違い、super.メソッド() はメソッドのどの位置にも書けます。
super.を付け忘れるとどうなるか
class Member {
String describe() { return "会員"; }
}
class PremiumMember extends Member {
@Override
String describe() {
return describe() + "(プレミアム)"; // super. を付け忘れた
}
}
public class Recursion {
public static void main(String[] args) {
try {
System.out.println(new PremiumMember().describe());
} catch (StackOverflowError e) {
System.out.println("StackOverflowError が発生しました");
}
}
}
実行結果:
StackOverflowError が発生しました
super. が無いと、describe() は自分自身を呼び続けます(this.describe() と同じ意味です)。コンパイルは通り、実行して初めて StackOverflowError になります。オーバーライドしたメソッドの中で同じ名前のメソッドを呼ぶときは、super. が付いているかを確かめましょう。
インターフェースのdefaultメソッドを呼ぶ書き方
2つのインターフェースが同じ名前のdefaultメソッドを持つ場合は、インターフェース名.super.メソッド名() でどちらを呼ぶかを指定します。
interface Greeter {
default String greet() { return "こんにちは"; }
}
interface Polite {
default String greet() { return "お世話になっております"; }
}
class Staff implements Greeter, Polite {
@Override
public String greet() {
// インターフェース名.super.メソッド() で、どちらの default かを選ぶ
return Greeter.super.greet() + "、" + Polite.super.greet();
}
}
public class Iface {
public static void main(String[] args) {
System.out.println(new Staff().greet());
}
}
実行結果:
こんにちは、お世話になっております
super.フィールドで親クラスのフィールドを参照する
親クラスと子クラスに同じ名前のフィールドがあると、子クラスのフィールドが親クラスのフィールドを隠します。super.フィールド名 と書くと、隠れている親クラス側を参照できます。
class Parent {
String label = "親のlabel";
}
class Child extends Parent {
String label = "子のlabel"; // 同じ名前のフィールド(親のものを隠す)
void show() {
String label = "ローカル変数のlabel";
System.out.println("label = " + label);
System.out.println("this.label = " + this.label);
System.out.println("super.label = " + super.label);
}
}
public class Field {
public static void main(String[] args) {
new Child().show();
Parent p = new Child();
System.out.println("Parent型の変数から p.label = " + p.label);
}
}
実行結果:
label = ローカル変数のlabel
this.label = 子のlabel
super.label = 親のlabel
Parent型の変数から p.label = 親のlabel
| 書き方 | 指すもの |
|---|---|
label |
いちばん近いもの(ここではローカル変数) |
this.label |
自分のクラス(Child)のフィールド |
super.label |
親クラス(Parent)のフィールド |
最後の行にも注目してください。メソッドは実際のインスタンスの型で決まりますが、フィールドは変数の型で決まります。 中身がChildでも、Parent型の変数から参照すると親のフィールドが見えます。この違いが混乱のもとになるため、親子で同じ名前のフィールドを作らず、名前を分けておくのがおすすめです。親のフィールドがprivateの場合は super. でも参照できないので、getterを使います。
this()とthis.の使い方
this()で同じクラスのコンストラクタを呼ぶ
this(引数) は、同じクラスの別のコンストラクタを呼びます。初期化の処理を1か所にまとめられるのが利点です。
class Task {
final String title;
final int priority;
Task() {
this("無題"); // 同じクラスの (String) へ
System.out.println("3. Task()");
}
Task(String title) {
this(title, 3); // 同じクラスの (String, int) へ
System.out.println("2. Task(String)");
}
Task(String title, int priority) {
// ここには暗黙の super() が入り、Object のコンストラクタが呼ばれる
this.title = title;
this.priority = priority;
System.out.println("1. Task(String, int)");
}
}
public class ThisCall {
public static void main(String[] args) {
Task t = new Task();
System.out.println(t.title + " / 優先度 " + t.priority);
}
}
実行結果:
1. Task(String, int)
2. Task(String)
3. Task()
無題 / 優先度 3
new Task() → this("無題") → this(title, 3) と呼び出しが進み、いちばん奥のコンストラクタから順に本体が実行されます。this.title = title; の this. は、引数の title とフィールドの title を区別するためのものです。
this()が互いを呼び合うとき
class Task {
Task() { this("無題"); }
Task(String title) { this(); }
}
public class Loop {
public static void main(String[] args) { new Task(); }
}
実行結果(コンパイルエラー):
Loop.java:3: error: recursive constructor invocation
Task(String title) { this(); }
^
1 error
コンストラクタ同士が呼び合って終わらなくなる書き方は、コンパイルの時点で知らせてくれます。this(...) の連鎖は、最後に必ず super(...)(暗黙を含む)へたどり着くように書きます。
そのほかのエラーと直し方
staticメソッドでsuperを使ったとき
class Parent {
void hello() { System.out.println("hello"); }
}
public class StaticCtx extends Parent {
public static void main(String[] args) {
super.hello();
}
}
実行結果(コンパイルエラー):
StaticCtx.java:6: error: non-static variable super cannot be referenced from a static context
super.hello();
^
1 error
staticメソッドはインスタンスなしで呼ばれるため、「自分のインスタンスの親の部分」を指す super を使えません。this も同じです。インスタンスを作ってから new StaticCtx().hello() のように呼びます。
super.super.と書いたとき
class A { void hi() { System.out.println("A"); } }
class B extends A { @Override void hi() { System.out.println("B"); } }
class C extends B {
@Override void hi() { super.super.hi(); }
}
public class SuperSuper {
public static void main(String[] args) { new C().hi(); }
}
実行結果(コンパイルエラー):
SuperSuper.java:4: error: <identifier> expected
@Override void hi() { super.super.hi(); }
^
SuperSuper.java:4: error: not a statement
@Override void hi() { super.super.hi(); }
^
2 errors
super で指せるのは1つ上の親クラスまでです。2つ上のクラスの処理が必要なときは、間のクラス(B)に、その処理を呼ぶメソッドを用意します。
エラーメッセージ早見表
| エラーメッセージ | 原因 | 直し方 |
|---|---|---|
constructor X in class X cannot be applied to given types |
親に引数なしコンストラクタが無いのに、暗黙の super(); が呼ばれた |
子クラスで super(引数) を明示する |
call to super must be first statement in constructor |
super() の前に文を書いた(Java 24まで) |
super() を先頭に移す。チェックはstaticメソッドに切り出す |
redundant explicit constructor invocation |
this() と super() を両方書いた(Java 25での表示) |
どちらか1つにする |
recursive constructor invocation |
this() が互いを呼び合っている |
連鎖の終点になるコンストラクタを決める |
non-static variable super cannot be referenced from a static context |
staticメソッドで super を使った |
インスタンスメソッドから使う |
cannot reference this before supertype constructor has been called |
super() の前でインスタンスのフィールドを読んだ |
super() の後ろに移す |
StackOverflowError(実行時) |
super. を付け忘れて自分自身を呼び続けた |
super.メソッド() に直す |
superとthisの使い分け判断表
| やりたいこと | 使うもの |
|---|---|
| 親クラスのフィールドをコンストラクタで初期化したい | super(引数) |
| 引数の少ないコンストラクタから、多いコンストラクタへ処理をまとめたい | this(引数) |
| オーバーライドしたメソッドで、親の処理も実行したい | super.メソッド() |
| 引数とフィールドが同じ名前なので区別したい | this.フィールド |
| 自分自身をほかのメソッドに渡したい | this |
| 子と同名の親フィールドを参照したい | super.フィールド(名前を分けるほうがおすすめ) |
| 同名のdefaultメソッドのどちらかを選んで呼びたい | インターフェース名.super.メソッド() |
| 親に渡す引数を先にチェックしたい | Java 24まで:staticメソッドを super(...) の引数で呼ぶ / Java 25以降:super() の前に書く |
よくある質問
Q. super()は毎回書いたほうがよいですか?
A. 引数なしの super(); は書かなくてもコンパイラが補うため、省略して問題ありません。親クラスのコンストラクタに引数が必要なときは super(引数) を必ず書きます。チーム内で「明示する」「省略する」のどちらかにそろえておくと読みやすくなります。
Q. super()とsuper.メソッド()は何が違いますか?
A. super() は親クラスのコンストラクタの呼び出しで、コンストラクタの中にだけ書けます。super.メソッド() は親クラスのメソッドの呼び出しで、インスタンスメソッドやコンストラクタの中のどの位置にも書けます。カッコが直後に付くか、ドットが付くかで見分けます。
Q. Java 21を使っています。super()の前に引数をチェックするにはどうすればよいですか?
A. チェック用のstaticメソッドを作り、super(check(name)) のように引数の中で呼びます。本記事のValidate21.javaをOpenJDK 21.0.10で実行し、親クラスのコンストラクタが動く前に例外が投げられることを確認しています。Java 25以降では、super() の前にif文を直接書けます。
Q. 親クラスのprivateフィールドはsuper.で参照できますか?
A. 参照できません。privateは「そのクラスの中だけ」という意味で、子クラスからも見えません。親クラスに用意されたgetterを呼ぶか、コンストラクタの super(引数) を通して値を渡します。子クラスから直接使わせたいフィールドは、親クラス側でprotectedにする方法もあります。
まとめ
- superは親クラスを、thisは自分自身を指すキーワード。カッコ付きはコンストラクタ呼び出し、ドット付きはメンバーへのアクセス
- インスタンスは親クラス → 子クラスの順に初期化される。
new Child()でも親のコンストラクタが先に動く super(...)もthis(...)も書かなければ、引数なしのsuper();が先頭に自動で入る- 親に引数なしコンストラクタが無いと
cannot be applied to given typesのエラーになる。子クラスでsuper(引数)を明示して直す super()の前の文は、Java 21ではcall to super must be first statementのエラー、Java 25では正式に書ける(JEP 513)- Java 25でも、
super()の前でインスタンスのフィールドを読んだりメソッドを呼んだりはできない。引数のチェックと自クラスのフィールドへの代入が中心 super.メソッド()のsuper.を付け忘れると自分自身を呼び続けてStackOverflowErrorになる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java superとは?super()とthisの違い・親クラス呼び出し」
株式会社RJCでは、Javaのsuperやthisを使った継承の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。