コンテンツにスキップ

9-2. 演習問題


1-1. 次のコードの出力はどれか。

public class Animal {
public void speak() { System.out.println("..."); }
}
public class Cat extends Animal {
@Override
public void speak() { System.out.println("ニャー"); }
}
Animal a = new Cat();
a.speak();
  • A. "..."
  • B. "ニャー"
  • C. コンパイルエラー
  • D. NullPointerException
解答と解説

正解:B

これがポリモーフィズムの核心だ。変数 a の型は Animal だが、実際に格納されているオブジェクトは Cat のインスタンス。Javaはメソッド呼び出しを 実行時 に実際のオブジェクトの型に基づいて解決する(動的ディスパッチ)。

そのため a.speak()Catspeak() が呼ばれ "ニャー" が出力される。

これにより、同じ Animal 型として扱いながらも、それぞれの動物が異なる振る舞いをするコードが書ける。


1-2. インターフェースと抽象クラスの違いとして正しいものはどれか。

  • A. インターフェースはインスタンス化できるが、抽象クラスはできない
  • B. インターフェースは複数実装できるが、クラスは1つしか継承できない
  • C. 抽象クラスはメソッドの実装を持てないが、インターフェースは持てる
  • D. インターフェースはフィールドを持てるが、抽象クラスは持てない
解答と解説

正解:B

  • A:どちらもインスタンス化できない
  • B:正しいimplements InterfaceA, InterfaceB のように複数実装可能。クラスの継承は extends ParentClass の1つのみ
  • C:逆。抽象クラスはメソッドの実装を持てる。インターフェースも Java 8以降は default メソッドで実装を持てる
  • D:逆。抽象クラスはフィールドを持てる。インターフェースは public static final 定数のみ

使い分けの基準:

  • 継承(is-a):「AはBの一種」→ 抽象クラス
  • 機能契約(can-do):「Aはこの機能を持つ」→ インターフェース

1-3. @Override アノテーションをつけずにメソッドをオーバーライドした場合どうなるか。

  • A. コンパイルエラーになる
  • B. 実行時エラーになる
  • C. 動作はするが、コンパイラがオーバーライドのチェックをしてくれない
  • D. 親クラスのメソッドが呼ばれるようになる
解答と解説

正解:C

@Overrideオーバーライドの意図をコンパイラに伝えるためのアノテーションであり、省略してもメソッドは正常にオーバーライドされる。

しかし @Override がないと:

  • 親クラスのメソッド名を typo しても「新しいメソッドの定義」として扱われ、オーバーライドになっていないことに気づけない
  • 親クラスのメソッドシグネチャが変わったときに検出できない

これらのバグを防ぐため、オーバーライドするときは必ず @Override を付けるのが良いプラクティス。


1-4. final キーワードの説明として誤っているものはどれか。

  • A. final クラスは継承できない
  • B. final メソッドはオーバーライドできない
  • C. final 変数は一度代入したら変更できない
  • D. final フィールドは static でなければならない
解答と解説

正解:D

final フィールドは static である必要はない。static final はクラス定数(Math.PI など)として使われるが、インスタンスの final フィールドはコンストラクタで初期化できる。

public class Circle {
private final double radius; // static でない final フィールド
public Circle(double radius) {
this.radius = radius; // コンストラクタで一度だけ設定できる
}
// this.radius = 5.0; // コンストラクタ外での再代入はエラー
}

final の3つの用途:

  1. final 変数・フィールド:再代入禁止
  2. final メソッド:オーバーライド禁止
  3. final クラス:継承禁止

2. 次のコードの( )に入るコードを答えよ。

// (1) Animal を継承した Dog クラス
public class Dog (1) Animal {
public Dog(String name) {
2)(name); // 親クラスのコンストラクタを呼ぶ
}
3// オーバーライドを明示するアノテーション
public void speak() {
System.out.println(name + " がワンワン!");
}
}
// (2) Printable インターフェースを実装した Report クラス
public class Report (4) Printable {
@Override
public void print() {
System.out.println("レポートを印刷します");
}
}
解答欄
解答と解説
  1. extends
  2. super
  3. @Override
  4. implements

解説

(1) extends でスーパークラスを指定して継承する。単一継承のみ可能。

(2) super(引数) でスーパークラスのコンストラクタを呼ぶ。サブクラスのコンストラクタの先頭(最初の文)でなければならない。

(3) @Override はコンパイラへの「これはオーバーライドである」という宣言。メソッド名や引数が親と一致しない場合にエラーを出してくれる。

(4) implements でインターフェースを実装する。複数のインターフェースを implements A, B, C のようにカンマ区切りで実装できる。

3-1. 次のクラス設計に問題点があればそれを指摘し、改善案を示せ。

public class BankAccount {
public int balance; // 残高
public void deposit(int amount) {
balance += amount;
}
public void withdraw(int amount) {
balance -= amount;
}
}
解答欄
解答と解説

問題点:

  1. balancepublic のため、外部から直接書き換えられる

    BankAccount account = new BankAccount();
    account.balance = -1000000; // 負の残高にできてしまう
  2. withdraw にバリデーションがなく、残高以上の引き出しができてしまう

改善後のコード:

public class BankAccount {
private int balance; // private でカプセル化
public BankAccount(int initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("初期残高は0以上");
this.balance = initialBalance;
}
public int getBalance() { return balance; } // 読み取り専用
public void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException("入金額は正の数");
balance += amount;
}
public void withdraw(int amount) {
if (amount <= 0) throw new IllegalArgumentException("出金額は正の数");
if (amount > balance) throw new IllegalStateException("残高不足");
balance -= amount;
}
}

カプセル化の効果:

  • 残高は deposit/withdraw メソッドを通じてのみ変更できる
  • バリデーションにより不正な状態(負の残高など)を防げる
  • 将来、ロギングや通知の追加も deposit/withdraw メソッドを修正するだけでよい

3-2. Shape(抽象クラス)を継承した Triangle クラスを実装せよ。コンストラクタは底辺(base)と高さ(height)を受け取り、area()底辺 × 高さ / 2 を返すこと。

public abstract class Shape {
protected String color;
public Shape(String color) { this.color = color; }
public abstract double area();
}
解答欄
解答と解説
public class Triangle extends Shape {
private double base;
private double height;
public Triangle(String color, double base, double height) {
super(color); // 親クラスのコンストラクタで color を初期化
this.base = base;
this.height = height;
}
@Override
public double area() {
return base * height / 2;
}
}
// 使用例
Triangle t = new Triangle("", 6.0, 4.0);
System.out.println(t.area()); // 12.0
System.out.printf("色: %s, 面積: %.1f%n", t.color, t.area());
// "色: 緑, 面積: 12.0"

super(color) で親クラスの color フィールドを初期化する必要がある点が重要だ。colorprotected のため直接アクセスもできるが、super() を使うのがOOPの慣習。

下の Java WASM プレイグラウンドで Product クラスを完成させ、カプセル化と例外の振る舞いを確認せよ。

  1. Product クラスのフィールド・コンストラクタ・メソッドを実装する
  2. toString() をオーバーライドして、指定形式で表示できるようにする
  3. purchase(20) 実行時に、在庫不足の例外が起きることを確認する
  4. 例外メッセージを変えて、自分で挙動を観察してみる
Java WASM Playground
ブラウザ内で Java をコンパイルして実行します / 編集内容は自動保存されます / Ctrl+Enter でも実行できます
使い方: 1ファイルならそのまま編集できます。複数ファイルに分けたい場合は // File: Main.java のような行で区切ってください。
出力
「実行」を押すと、ここにコンパイル結果と実行結果が表示されます。
  • フィールドを private にすることで、どんな不正操作を防げるか
  • 正常系と異常系をメソッドで分けて考えられるか
  • toString() を使うとデバッグ出力が読みやすくなる理由