Java Silver試験で頻出の「継承とポリモーフィズム」に関する問題を10問収録しました。extends、super()、メソッドオーバーライド、多態性、instanceof、Objectクラスのメソッドなど、試験で問われやすいポイントを網羅しています。
問題1 ⭐(基本)
次のコードをコンパイル・実行すると、どうなりますか?
class Animal {
void speak() {
System.out.println("...");
}
}
class Dog extends Animal {
void speak() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.speak();
}
}
- A.
... - B.
Woof! - C. コンパイルエラー
- D. 実行時エラー
解答と解説
正解: B
これはポリモーフィズム(多態性)の基本例です。変数aの型はAnimalですが、実際のオブジェクトはDogです。インスタンスメソッドの呼び出しは実行時のオブジェクトの型(Dog)に基づいて解決されるため、Dogのspeak()が呼ばれます。
これを「動的束縛(dynamic binding)」または「動的ディスパッチ」と呼びます。
問題2 ⭐⭐(応用)
次のコードをコンパイル・実行すると、どうなりますか?
class Parent {
Parent() {
System.out.println("Parent constructor");
}
}
class Child extends Parent {
Child() {
System.out.println("Child constructor");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
}
}
- A.
Child constructor - B.
Parent constructor - C.
Parent constructor→Child constructor - D.
Child constructor→Parent constructor
解答と解説
正解: C
サブクラスのコンストラクタの先頭には、明示的にsuper()やthis()を書かなければ、コンパイラが自動的にsuper()(親クラスの引数なしコンストラクタ呼び出し)を挿入します。
そのため、Childのコンストラクタは実質的に以下と同じです:
Child() {
super(); // コンパイラが自動挿入
System.out.println("Child constructor");
}
親クラスのコンストラクタが先に実行され、その後に子クラスのコンストラクタの残りが実行されます。
問題3 ⭐⭐(応用)
次のコードをコンパイル・実行すると、どうなりますか?
class Base {
Base(int x) {
System.out.println("Base: " + x);
}
}
class Derived extends Base {
Derived() {
System.out.println("Derived");
}
}
public class Main {
public static void main(String[] args) {
Derived d = new Derived();
}
}
- A.
Base: 0→Derived - B.
Derived - C. コンパイルエラー
- D. 実行時エラー
解答と解説
正解: C
Baseクラスには引数ありのコンストラクタBase(int x)のみが定義されているため、デフォルトコンストラクタ(引数なし)は自動生成されません。Derivedのコンストラクタには明示的なsuper()呼び出しがないため、コンパイラがsuper()(引数なし)を自動挿入しようとしますが、Baseに引数なしコンストラクタが存在しないためコンパイルエラーになります。
修正するには、Derived()内でsuper(何らかのint値)を明示的に呼び出す必要があります。
問題4 ⭐⭐⭐(チャレンジ)
次のコードをコンパイル・実行すると、どうなりますか?
class Parent {
protected Number getValue() {
return 42;
}
}
class Child extends Parent {
@Override
protected Integer getValue() {
return 100;
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.getValue());
}
}
- A.
42 - B.
100 - C. コンパイルエラー(戻り値の型が一致しない)
- D. 実行時エラー
解答と解説
正解: B
Java 5以降、オーバーライドの戻り値に**共変戻り値型(covariant return type)**が認められています。IntegerはNumberのサブクラスなので、戻り値の型をNumberからIntegerに変更することは正当なオーバーライドです。
ポリモーフィズムにより、実行時のオブジェクト型ChildのgetValue()が呼ばれ、100が出力されます。
なお、共変戻り値型はプリミティブ型には適用されません。intからlongへの変更はオーバーライドとして認められません。
問題5 ⭐⭐(応用)
次のコードをコンパイル・実行すると、どうなりますか?
class Animal {
String type = "Animal";
String getType() {
return type;
}
}
class Cat extends Animal {
String type = "Cat";
String getType() {
return type;
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Cat();
System.out.println(a.type);
System.out.println(a.getType());
}
}
- A.
AnimalとAnimal - B.
CatとCat - C.
AnimalとCat - D.
CatとAnimal
解答と解説
正解: C
フィールドとメソッドでは動作が異なります:
-
フィールドアクセス: コンパイル時の変数の型(宣言型)で決まる →
a.typeはAnimalのtypeを参照 →"Animal" -
メソッド呼び出し: 実行時のオブジェクトの型で決まる(動的束縛) →
a.getType()はCatのgetType()が呼ばれる →"Cat"
フィールドにはポリモーフィズムが適用されません。フィールドの「隠蔽(hiding)」とメソッドの「オーバーライド(overriding)」は異なる概念です。
問題6 ⭐(基本)
次のコードをコンパイル・実行すると、どうなりますか?
class Vehicle {}
class Car extends Vehicle {}
class Truck extends Vehicle {}
public class Main {
public static void main(String[] args) {
Vehicle v = new Car();
System.out.println(v instanceof Vehicle);
System.out.println(v instanceof Car);
System.out.println(v instanceof Truck);
}
}
- A.
true→true→true - B.
true→true→false - C.
true→false→false - D. コンパイルエラー
解答と解説
正解: B
instanceofは実行時のオブジェクトの型を確認します。vの実際のオブジェクトはCarです。
-
v instanceof Vehicle→CarはVehicleのサブクラスなのでtrue -
v instanceof Car→ オブジェクトはCarなのでtrue -
v instanceof Truck→CarはTruckではないのでfalse
instanceofはオブジェクトが指定した型か、そのサブクラスのインスタンスである場合にtrueを返します。nullに対しては常にfalseを返します。
問題7 ⭐⭐⭐(チャレンジ)
次のコードをコンパイル・実行すると、どうなりますか?
class Base {
void show() {
System.out.println("Base");
}
}
class Sub extends Base {
@Override
void show() {
System.out.println("Sub");
}
}
public class Main {
public static void main(String[] args) {
Base b = new Sub();
b.show();
Base b2 = (Base) b;
b2.show();
}
}
- A.
Sub→Base - B.
Sub→Sub - C.
Base→Base - D.
Base→Sub
解答と解説
正解: B
キャストは参照の型を変えるだけであり、実際のオブジェクトは変わりません。(Base) bでBase型にキャストしても、オブジェクト自体は依然としてSubのインスタンスです。
メソッド呼び出しは実行時のオブジェクトの型に基づいて解決されるため、どちらの場合もSubのshow()が呼ばれます。
キャストはコンパイル時の型チェックに影響しますが、実行時のポリモーフィズムには影響しません。
問題8 ⭐⭐(応用)
次のコードをコンパイル・実行すると、どうなりますか?
class Parent {
private void greet() {
System.out.println("Hello from Parent");
}
void callGreet() {
greet();
}
}
class Child extends Parent {
void greet() {
System.out.println("Hello from Child");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
c.callGreet();
}
}
- A.
Hello from Child - B.
Hello from Parent - C. コンパイルエラー
- D. 実行時エラー
解答と解説
正解: B
Parentのgreet()はprivateなので、サブクラスChildからは見えません。Childのgreet()はオーバーライドではなく、まったく別のメソッドです。
callGreet()はParentクラスで定義されており、Parentのprivate greet()を呼び出します。privateメソッドはポリモーフィズムの対象外で、コンパイル時に静的に解決されます。
privateメソッド、staticメソッド、finalメソッドはオーバーライドできません。
問題9 ⭐⭐⭐(チャレンジ)
次のコードをコンパイル・実行すると、どうなりますか?
class Shape {
@Override
public String toString() {
return "Shape";
}
@Override
public boolean equals(Object obj) {
return obj instanceof Shape;
}
@Override
public int hashCode() {
return 1;
}
}
public class Main {
public static void main(String[] args) {
Shape s1 = new Shape();
Shape s2 = new Shape();
System.out.println(s1.toString());
System.out.println(s1.equals(s2));
System.out.println(s1.equals("hello"));
}
}
- A.
Shape→true→true - B.
Shape→true→false - C.
Shape→false→false - D. コンパイルエラー
解答と解説
正解: B
Objectクラスの3つの重要なメソッドをオーバーライドしています。
-
s1.toString()→ オーバーライドされたtoString()が呼ばれ"Shape"を返す -
s1.equals(s2)→s2はShapeのインスタンスなのでobj instanceof Shapeはtrue -
s1.equals("hello")→"hello"はStringのインスタンスでありShapeではないのでobj instanceof Shapeはfalse
equals()をオーバーライドする場合はhashCode()も必ずオーバーライドすべきです(契約: equals()がtrueならhashCode()も同じ値を返す必要がある)。
問題10 ⭐⭐⭐(チャレンジ)
次のコードをコンパイル・実行すると、どうなりますか?
class A {
void method() throws Exception {
System.out.println("A");
}
}
class B extends A {
@Override
void method() throws RuntimeException {
System.out.println("B");
}
}
class C extends A {
@Override
void method() {
System.out.println("C");
}
}
public class Main {
public static void main(String[] args) throws Exception {
A obj = new B();
obj.method();
}
}
- A.
A - B.
B - C.
C - D. コンパイルエラー
解答と解説
正解: B
オーバーライド時のthrows宣言ルール:
-
同じ例外またはそのサブクラスをスローできる →
RuntimeExceptionはExceptionのサブクラスなのでOK -
例外をスローしないようにもできる → クラス
Cのようにthrows宣言を省略するのもOK -
親クラスより広い例外をスローすることはできない →
throws Throwableにするとコンパイルエラー
Bのmethod()はRuntimeExceptionを宣言していますが、RuntimeExceptionはExceptionのサブクラスなので正当なオーバーライドです。ポリモーフィズムによりBのmethod()が呼ばれ、"B"が出力されます。
参考
- Oracle Java SE 17 Language Specification - Chapter 8.4.8: Inheritance, Overriding, and Hiding
- Oracle Java Tutorials - Inheritance
- Oracle Java Tutorials - Polymorphism
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!