💬

【Java SE 17 Silver】継承とインターフェースの使用の問題3選(問題+解説付き)3

に公開

継承とインターフェースの使用の問題

問題1

次のプログラムをコンパイルして実行したときの結果として、正しいものを選んでください。(1つ選択)

// Parent.java
class Parent {
    String label = "P";

    void show() {
        System.out.print(label);
    }
}

class Child extends Parent {
    String label = "C";
}
// Main.java
public class Main {
    public static void main(String[] args) {
        Parent a = new Parent();
        Parent b = new Child();

        System.out.print(a.label);
        System.out.print(b.label);
        a.show();
        b.show();
    }
}

選択肢

  • A. PPCP と表示される
  • B. PPPP と表示される
  • C. PCCP と表示される
  • D. CCCC と表示される
  • E. コンパイルエラーが発生する
  • F. 実行時に例外がスローされる

正解は、

↓

↓

↓

↓

↓

↓

↓

↓
✅ 正解:B. PPPP と表示される

フィールド(変数)とメソッドの違い

Javaでは メソッドは「オーバーライド(上書き)」できて、
フィールド(変数) は「オーバーライド」ではなく “隠す(hide)” という動きになる。

  • メソッド:実行時に「どのクラスのメソッドか」が決まる(多態性)
  • フィールド:コンパイル時に「参照型(左側の型)」で決まる(多態性しない)

処理の順番(何が起きてるか)

1) まずインスタンス生成

Parent a = new Parent();
Parent b = new Child();
  • a は Parent型で Parentのインスタンス
  • b は Parent型だけど Childのインスタンス(中身はChild)

2) フィールド参照は「左側の型」で決まる

System.out.print(a.label);
System.out.print(b.label);

ここがひっかけ!

  • a は Parent 型 → Parent.label を参照 → P
  • b も 左側が Parent 型 → Parent.label を参照 → P

Childにも label = "C" はあるが、
b は Parent b だから Parent.label が選ばれる。

ここまでで出力は:

PP

3) メソッド呼び出しは「実体(newした方)」で決まる

a.show();
b.show();

メソッドは基本的に「実体がどのクラスか」で決まる(多態性)。

ただし今回、Child は show() を オーバーライドしていないので、

  • a.show() → Parent.show() → P
  • b.show() → Childにshowがない → Parent.show() → P

さらに、Parent.show() の中の label は Parentのフィールドを指すので、
Child のインスタンスであっても Parent.label(= P)が表示される。

追加の出力は:

PP

合計して:

PPPP

じゃあ C を表示したいなら

方法は2つある。

直し方1:参照型を Child にする(フィールドを見るとき)

Child c = new Child();
System.out.print(c.label); // C

直し方2:show() をオーバーライドする(メソッドで差を出す)

class Child extends Parent {
    String label = "C";

    @Override
    void show() {
        System.out.print(label); // ここでは Child.label を参照
    }
}

この場合は Parent b = new Child(); でも b.show() は C を表示する。

まとめ(覚え方)

  • フィールドは多態性しない(左側の型で決まる)
  • メソッドは多態性する(実体のクラスで決まる)
  • 同名フィールドは「上書き」ではなく 隠れる(hiding)

問題2

次のプログラムの // ここに入れる に記述して コンパイルできるコードとして、正しいものを選んでください。(1つ選択)

// Helper.java
public interface Helper {
    // ここに入れる
}

選択肢

  • A. private int calc();
  • B.
    private int calc(int x) { return x * 2; }
  • C.
    private default int calc(int x) { return x * 2; }
  • D. 上記の選択肢はいずれもコンパイルエラーとなる

正解は、

↓

↓

↓

↓

↓

↓

↓

↓
✅ 正解:B

interface のメソッドって基本どうなるの?

interface のメソッドは、何も修飾子を書かないと基本的に

  • public
  • abstract

として扱われます(=本体 {} を書かずに宣言だけする形が多い)。

例:

interface I {
    void run(); // ← public abstract void run(); とほぼ同じ扱い
}

今回のポイント:private メソッドは 本体が必須

Java 9 から、interface に private メソッドを書けるようになりました。

ただし、private メソッドは

  • 外から呼ばれない(interface の内部用)
  • だから 必ず実装(本体)が必要

というルールがあります。

各選択肢の判定

A. private int calc(); はダメ

private int calc();

これは 本体がない(=抽象メソッド)ですよね。

でも private は「外から呼べない」ので、
抽象のまま残して「実装はクラス側に任せる」ができません。

✅ 結果:コンパイルエラー

B. private int calc(int x) { ... } はOK

private int calc(int x) {
    return x * 2;
}

private メソッドとして 本体があるのでコンパイルできます。

✅ 結果:コンパイルOK(正解)

C. private default ... はダメ

private default int calc(int x) {
    return x * 2;
}

default は「実装を持つ public なメソッド(クラスがそのまま使える)」という意味です。

一方 private は「外から見えない」ので、両立しません。

✅ 結果:コンパイルエラー

private メソッドは何のためにあるの?

default メソッドが増えると、同じ処理を何回も書きがちになります。

その共通処理を private に切り出すために使います。

例:

interface Helper {
    default int doubleThenAddOne(int x) {
        return doubleValue(x) + 1;
    }

    default int doubleThenAddTwo(int x) {
        return doubleValue(x) + 2;
    }

    // 共通処理を private にできる(Java 9+)
    private int doubleValue(int x) {
        return x * 2;
    }
}

まとめ(覚え方)

  • interface の private メソッドは Java 9+
  • private メソッドは必ず本体 {} が必要(抽象にできない)
  • private と default は 同時に使えない

問題3

次のプログラムをコンパイルして実行したときの結果として、正しいものを選んでください。(1つ選択)

// Base.java
class Base {
    public Base() {
        System.out.println("1");
    }

    public Base(String msg) {
        this();
        System.out.println(msg);
    }
}
// Sub.java
class Sub extends Base {
    public Sub() {
        super("2");
        System.out.println("3");
    }

    public Sub(String msg) {
        this();
        System.out.println(msg);
    }
}
// Main.java
public class Main {
    public static void main(String[] args) {
        new Sub("4");
    }
}

選択肢

  • A. 1 → 2 → 3 → 4 と表示される
  • B. 2 → 1 → 3 → 4 と表示される
  • C. 1 → 3 → 2 → 4 と表示される
  • D. 1 → 2 → 4 → 3 と表示される
  • E. コンパイルエラーが発生する
  • F. 実行時に例外がスローされる

正解は、

↓

↓

↓

↓

↓

↓

↓

↓
✅ 正解:A. 1 → 2 → 3 → 4 と表示される

解説(初心者向けに順番を丁寧に)

この問題のテーマは、

  • this(...) は 同じクラスの別コンストラクタへ移動する
  • super(...) は 親クラスのコンストラクタへ移動する
  • そして コンストラクタの最初の1行でしか this(...) / super(...) は呼べない

という「呼び出しの連鎖」を追えるかどうか。

ステップ1:どのコンストラクタから始まる?

Main で実行しているのはこれ:

new Sub("4");

だから最初は Sub(String msg) が呼ばれる。

ステップ2:Sub(String msg) の中身を見る

public Sub(String msg) {
    this();              // ← Sub() に移動
    System.out.println(msg);
}

最初の行が this() なので、いったん Sub() に移動します。

ステップ3:Sub() の最初は super("2")

public Sub() {
    super("2");          // ← Base(String) に移動
    System.out.println("3");
}

ここで親クラス Base の Base(String msg) に移動します。

ステップ4:Base(String msg) では this() が先に動く

public Base(String msg) {
    this();              // ← Base() に移動
    System.out.println(msg);
}

this() があるので、まず Base() に移動します。

ステップ5:Base() が 1 を表示

public Base() {
    System.out.println("1");
}

✅ ここで 1 が表示されます。

ここから「戻りながら」表示される(大事!)

Base() が終わる → 呼び出し元の Base(String) に戻る
→ System.out.println(msg); が実行(msg は "2")
✅ 2 が表示

Base(String) が終わる → 呼び出し元の Sub() に戻る
→ System.out.println("3"); が実行
✅ 3 が表示

Sub() が終わる → 呼び出し元の Sub("4") に戻る
→ System.out.println(msg); が実行(msg は "4")
✅ 4 が表示

結果(表示順)

1
2
3
4

だから選択肢は A ✅

まとめ(覚え方)

  • this(...):同じクラス内の別コンストラクタへ
  • super(...):親クラスのコンストラクタへ
  • どちらも コンストラクタの先頭行にしか書けない
  • 表示は「呼び出しが深く潜ってから、戻りながら出る」ので順番に注意!

Discussion