【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 のメソッドは、何も修飾子を書かないと基本的に
publicabstract
として扱われます(=本体 {} を書かずに宣言だけする形が多い)。
例:
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