はじめに
Java研修コーディング問題集の第8回は 継承とインターフェース です。
継承はオブジェクト指向の柱の一つで、既存のクラスを拡張して新しいクラスを作る仕組みです。インターフェースと合わせて、ポリモーフィズム(多態性)の概念を理解しましょう。
難易度の見方
| マーク | 難易度 | 目安 |
|---|---|---|
| ⭐ | 基本 | 研修1週目レベル |
| ⭐⭐ | 応用 | 少し考える必要あり |
| ⭐⭐⭐ | チャレンジ | 複数の知識を組み合わせる |
問題1:基本的な継承 ⭐
問題
Animal(動物)クラスを親クラスとして、Dog(犬)と Cat(猫)の子クラスを作成してください。
-
Animal:nameフィールドとeat()メソッド -
Dog:bark()メソッドを追加 -
Cat:meow()メソッドを追加
期待する出力
ポチが食事中...
ワンワン!
タマが食事中...
ニャー!
模範解答
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + "が食事中...");
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
void bark() {
System.out.println("ワンワン!");
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
void meow() {
System.out.println("ニャー!");
}
}
public class Exercise01 {
public static void main(String[] args) {
Dog dog = new Dog("ポチ");
dog.eat();
dog.bark();
Cat cat = new Cat("タマ");
cat.eat();
cat.meow();
}
}
ポイント: extends で継承を宣言します。子クラスは親クラスのフィールドとメソッドを引き継ぎます。super(name) で親クラスのコンストラクタを呼び出しています。
問題2:メソッドのオーバーライド ⭐
問題
Animal クラスに sound() メソッドを追加し、各子クラスでオーバーライドしてください。
期待する出力
ポチの鳴き声: ワンワン!
タマの鳴き声: ニャー!
ピーちゃんの鳴き声: ピヨピヨ!
模範解答
class Animal {
String name;
Animal(String name) {
this.name = name;
}
String sound() {
return "...";
}
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override
String sound() {
return "ワンワン!";
}
}
class Cat extends Animal {
Cat(String name) { super(name); }
@Override
String sound() {
return "ニャー!";
}
}
class Bird extends Animal {
Bird(String name) { super(name); }
@Override
String sound() {
return "ピヨピヨ!";
}
}
public class Exercise02 {
public static void main(String[] args) {
Animal[] animals = {
new Dog("ポチ"),
new Cat("タマ"),
new Bird("ピーちゃん")
};
for (Animal a : animals) {
System.out.println(a.name + "の鳴き声: " + a.sound());
}
}
}
ポイント: オーバーライドは親クラスのメソッドを子クラスで再定義することです。Animal 型の変数に Dog や Cat のインスタンスを入れても、実際のオブジェクトのメソッドが呼ばれます。これが ポリモーフィズム(多態性) です。
問題3:super キーワード ⭐⭐
問題
Vehicle(乗り物)クラスとその子クラス ElectricCar(電気自動車)を作成してください。ElectricCar は Vehicle の getInfo() を拡張して、バッテリー情報も含めてください。
期待する出力
--- 普通の車 ---
車名: カローラ、速度: 180km/h
--- 電気自動車 ---
車名: テスラ、速度: 250km/h
バッテリー: 75kWh、航続距離: 400km
模範解答
class Vehicle {
protected String name;
protected int maxSpeed;
Vehicle(String name, int maxSpeed) {
this.name = name;
this.maxSpeed = maxSpeed;
}
void showInfo() {
System.out.println("車名: " + name + "、速度: " + maxSpeed + "km/h");
}
}
class ElectricCar extends Vehicle {
private int batteryCapacity;
private int range;
ElectricCar(String name, int maxSpeed, int batteryCapacity, int range) {
super(name, maxSpeed);
this.batteryCapacity = batteryCapacity;
this.range = range;
}
@Override
void showInfo() {
super.showInfo();
System.out.println("バッテリー: " + batteryCapacity + "kWh、航続距離: " + range + "km");
}
}
public class Exercise03 {
public static void main(String[] args) {
Vehicle car = new Vehicle("カローラ", 180);
ElectricCar ev = new ElectricCar("テスラ", 250, 75, 400);
System.out.println("--- 普通の車 ---");
car.showInfo();
System.out.println("\n--- 電気自動車 ---");
ev.showInfo();
}
}
ポイント: super.showInfo() で親クラスのメソッドを呼び出し、その後に追加の情報を出力しています。protected は同じパッケージと子クラスからアクセス可能なアクセス修飾子です。
問題4:抽象クラス ⭐⭐
問題
Shape(図形)抽象クラスを作成し、Circle(円)と Rectangle(長方形)で面積と周の長さを計算してください。
-
Shape(抽象クラス):abstract double area(),abstract double perimeter() -
Circle:半径 -
Rectangle:幅と高さ
期待する出力
=== 図形の計算 ===
円(半径5.0)
面積: 78.54
周の長さ: 31.42
長方形(幅4.0×高さ6.0)
面積: 24.00
周の長さ: 20.00
模範解答
abstract class Shape {
abstract double area();
abstract double perimeter();
abstract String describe();
}
class Circle extends Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
@Override
double perimeter() {
return 2 * Math.PI * radius;
}
@Override
String describe() {
return "円(半径" + radius + ")";
}
}
class Rectangle extends Shape {
private double width;
private double height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
@Override
double perimeter() {
return 2 * (width + height);
}
@Override
String describe() {
return "長方形(幅" + width + "×高さ" + height + ")";
}
}
public class Exercise04 {
public static void main(String[] args) {
Shape[] shapes = {
new Circle(5.0),
new Rectangle(4.0, 6.0)
};
System.out.println("=== 図形の計算 ===");
for (Shape s : shapes) {
System.out.println(s.describe());
System.out.printf(" 面積: %.2f%n", s.area());
System.out.printf(" 周の長さ: %.2f%n", s.perimeter());
System.out.println();
}
}
}
ポイント: 抽象クラス はインスタンス化できず、子クラスに実装を強制します。abstract メソッドは宣言のみで、実装は子クラスで行います。共通の「型」を定義したいが、実装は異なる場合に使います。
問題5:インターフェース ⭐⭐
問題
Printable(印刷可能)インターフェースを定義し、Report(レポート)と Invoice(請求書)で実装してください。
interface Printable {
void print();
int getPageCount();
}
期待する出力
=== 印刷ジョブ ===
--- レポート ---
[レポート] タイトル: 月次報告書
ページ数: 5ページ
--- 請求書 ---
[請求書] 請求先: 株式会社ABC
金額: 150000円
ページ数: 2ページ
合計ページ数: 7ページ
模範解答
interface Printable {
void print();
int getPageCount();
}
class Report implements Printable {
private String title;
private int pages;
Report(String title, int pages) {
this.title = title;
this.pages = pages;
}
@Override
public void print() {
System.out.println("[レポート] タイトル: " + title);
}
@Override
public int getPageCount() {
return pages;
}
}
class Invoice implements Printable {
private String clientName;
private int amount;
private int pages;
Invoice(String clientName, int amount, int pages) {
this.clientName = clientName;
this.amount = amount;
this.pages = pages;
}
@Override
public void print() {
System.out.println("[請求書] 請求先: " + clientName);
System.out.println("金額: " + amount + "円");
}
@Override
public int getPageCount() {
return pages;
}
}
public class Exercise05 {
public static void main(String[] args) {
Printable[] documents = {
new Report("月次報告書", 5),
new Invoice("株式会社ABC", 150000, 2)
};
String[] labels = {"レポート", "請求書"};
System.out.println("=== 印刷ジョブ ===");
int totalPages = 0;
for (int i = 0; i < documents.length; i++) {
System.out.println("--- " + labels[i] + " ---");
documents[i].print();
System.out.println("ページ数: " + documents[i].getPageCount() + "ページ");
totalPages += documents[i].getPageCount();
System.out.println();
}
System.out.println("合計ページ数: " + totalPages + "ページ");
}
}
ポイント: インターフェース は「何ができるか」を定義する契約です。implements で宣言し、すべてのメソッドを実装します。異なるクラスでも同じインターフェースを実装すれば、統一的に扱えます(ポリモーフィズム)。
問題6:複数インターフェースの実装 ⭐⭐
問題
以下の2つのインターフェースを定義し、SmartPhone クラスで両方を実装してください。
interface Callable {
void call(String number);
}
interface Photographable {
void takePhoto();
int getPhotoCount();
}
期待する出力
=== スマートフォン: iPhone ===
090-1234-5678 に電話中...
写真を撮影しました(合計: 1枚)
写真を撮影しました(合計: 2枚)
080-9876-5432 に電話中...
写真を撮影しました(合計: 3枚)
保存されている写真: 3枚
模範解答
interface Callable {
void call(String number);
}
interface Photographable {
void takePhoto();
int getPhotoCount();
}
class SmartPhone implements Callable, Photographable {
private String modelName;
private int photoCount;
SmartPhone(String modelName) {
this.modelName = modelName;
this.photoCount = 0;
}
@Override
public void call(String number) {
System.out.println(number + " に電話中...");
}
@Override
public void takePhoto() {
photoCount++;
System.out.println("写真を撮影しました(合計: " + photoCount + "枚)");
}
@Override
public int getPhotoCount() {
return photoCount;
}
String getModelName() {
return modelName;
}
}
public class Exercise06 {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone("iPhone");
System.out.println("=== スマートフォン: " + phone.getModelName() + " ===");
phone.call("090-1234-5678");
phone.takePhoto();
phone.takePhoto();
phone.call("080-9876-5432");
phone.takePhoto();
System.out.println("保存されている写真: " + phone.getPhotoCount() + "枚");
}
}
ポイント: Javaのクラスは複数のインターフェースを実装できます(多重継承は不可)。カンマ区切りで implements Callable, Photographable と書きます。1つのクラスが複数の「役割」を持てるのがインターフェースの強みです。
問題7:instanceof と型キャスト ⭐⭐
問題
Animal 型の配列に Dog, Cat, Bird のインスタンスを入れ、instanceof を使って型を判定し、型に応じた処理を行ってください。
期待する出力
=== 動物たちの特技 ===
ポチは犬です → お手!
タマは猫です → 毛づくろい中...
ピーちゃんは鳥です → 飛行中!
ハチは犬です → お手!
ミケは猫です → 毛づくろい中...
模範解答
class Animal {
String name;
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
Dog(String name) { super(name); }
void shake() { System.out.println("お手!"); }
}
class Cat extends Animal {
Cat(String name) { super(name); }
void groom() { System.out.println("毛づくろい中..."); }
}
class Bird extends Animal {
Bird(String name) { super(name); }
void fly() { System.out.println("飛行中!"); }
}
public class Exercise07 {
public static void main(String[] args) {
Animal[] animals = {
new Dog("ポチ"),
new Cat("タマ"),
new Bird("ピーちゃん"),
new Dog("ハチ"),
new Cat("ミケ")
};
System.out.println("=== 動物たちの特技 ===");
for (Animal a : animals) {
if (a instanceof Dog) {
System.out.print(a.name + "は犬です → ");
((Dog) a).shake();
} else if (a instanceof Cat) {
System.out.print(a.name + "は猫です → ");
((Cat) a).groom();
} else if (a instanceof Bird) {
System.out.print(a.name + "は鳥です → ");
((Bird) a).fly();
}
}
}
}
ポイント: instanceof でオブジェクトの実際の型を判定できます。(Dog) a のようにキャストすると、子クラス固有のメソッドを呼べます。Java 16以降では if (a instanceof Dog dog) とパターンマッチングが使えます。
問題8:デフォルトメソッドと関数型インターフェース ⭐⭐⭐
問題
Logger インターフェースにデフォルトメソッドを定義し、異なるログ出力先を実装してください。
interface Logger {
void log(String message);
default void info(String message) {
log("[INFO] " + message);
}
default void warn(String message) {
log("[WARN] " + message);
}
default void error(String message) {
log("[ERROR] " + message);
}
}
期待する出力
=== コンソールロガー ===
[INFO] アプリケーション起動
[WARN] メモリ使用率が80%を超えました
[ERROR] データベース接続エラー
=== ファイルロガー(シミュレーション)===
[FILE] [INFO] バッチ処理開始
[FILE] [ERROR] ファイルが見つかりません
模範解答
interface Logger {
void log(String message);
default void info(String message) {
log("[INFO] " + message);
}
default void warn(String message) {
log("[WARN] " + message);
}
default void error(String message) {
log("[ERROR] " + message);
}
}
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println(message);
}
}
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[FILE] " + message);
}
}
public class Exercise08 {
public static void main(String[] args) {
System.out.println("=== コンソールロガー ===");
Logger console = new ConsoleLogger();
console.info("アプリケーション起動");
console.warn("メモリ使用率が80%を超えました");
console.error("データベース接続エラー");
System.out.println("\n=== ファイルロガー(シミュレーション)===");
Logger file = new FileLogger();
file.info("バッチ処理開始");
file.error("ファイルが見つかりません");
}
}
ポイント: デフォルトメソッド(Java 8以降)により、インターフェースにも実装を持たせることができます。共通の振る舞いはデフォルトメソッドで提供し、各クラスは log() の実装だけを変えれば良いため、コードの重複を防げます。
問題9:テンプレートメソッドパターン ⭐⭐⭐
問題
データ処理の流れ(読み込み→変換→出力)を抽象クラスで定義し、CSV処理とJSON処理で具体的な実装を行ってください。
期待する出力
=== CSV処理 ===
[1] CSVファイルを読み込み中...
[2] CSVデータを変換中...(3件のレコード)
[3] CSVデータを出力中...
処理完了!(3ステップ)
=== JSON処理 ===
[1] JSONファイルを読み込み中...
[2] JSONデータを変換中...(パース実行)
[3] JSONデータを出力中...
処理完了!(3ステップ)
模範解答
abstract class DataProcessor {
// テンプレートメソッド
final void process() {
System.out.println("[1] " + readData());
System.out.println("[2] " + transformData());
System.out.println("[3] " + writeData());
System.out.println("処理完了!(3ステップ)");
}
abstract String readData();
abstract String transformData();
abstract String writeData();
}
class CsvProcessor extends DataProcessor {
@Override
String readData() {
return "CSVファイルを読み込み中...";
}
@Override
String transformData() {
return "CSVデータを変換中...(3件のレコード)";
}
@Override
String writeData() {
return "CSVデータを出力中...";
}
}
class JsonProcessor extends DataProcessor {
@Override
String readData() {
return "JSONファイルを読み込み中...";
}
@Override
String transformData() {
return "JSONデータを変換中...(パース実行)";
}
@Override
String writeData() {
return "JSONデータを出力中...";
}
}
public class Exercise09 {
public static void main(String[] args) {
System.out.println("=== CSV処理 ===");
DataProcessor csv = new CsvProcessor();
csv.process();
System.out.println("\n=== JSON処理 ===");
DataProcessor json = new JsonProcessor();
json.process();
}
}
ポイント: テンプレートメソッドパターンはデザインパターンの一つで、処理の骨組みを親クラスで定義し、詳細を子クラスに委ねます。final を付けることで、処理の流れ(順番)を子クラスが変更できないようにしています。
問題10:社員管理システム(継承の総合問題)⭐⭐⭐
問題
以下のクラス構成で社員管理システムを作成してください。
-
Employee(抽象クラス):名前、社員番号、基本給-
abstract int calculateSalary():給与計算
-
-
FullTimeEmployee:正社員(基本給 + ボーナス倍率) -
PartTimeEmployee:パート(時給 × 勤務時間) -
Manager:管理職(正社員を継承 + 管理手当) -
Payableインターフェース:void showPaySlip()
期待する出力
=== 給与明細一覧 ===
[正社員] 田中太郎(E001)
基本給: 300000円
ボーナス: 600000円
年収: 4200000円
[パート] 鈴木花子(E002)
時給: 1200円 × 120時間
月給: 144000円
[管理職] 佐藤部長(E003)
基本給: 400000円
管理手当: 80000円
ボーナス: 800000円
年収: 6560000円
--- 月間人件費合計 ---
正社員: 300000円
パート: 144000円
管理職: 480000円
合計: 924000円
模範解答
interface Payable {
void showPaySlip();
}
abstract class Employee implements Payable {
protected String name;
protected String employeeId;
protected int baseSalary;
Employee(String name, String employeeId, int baseSalary) {
this.name = name;
this.employeeId = employeeId;
this.baseSalary = baseSalary;
}
abstract int calculateMonthlySalary();
}
class FullTimeEmployee extends Employee {
private double bonusMultiplier;
FullTimeEmployee(String name, String employeeId, int baseSalary, double bonusMultiplier) {
super(name, employeeId, baseSalary);
this.bonusMultiplier = bonusMultiplier;
}
@Override
int calculateMonthlySalary() {
return baseSalary;
}
int calculateBonus() {
return (int) (baseSalary * bonusMultiplier);
}
int calculateAnnualSalary() {
return baseSalary * 12 + calculateBonus();
}
@Override
public void showPaySlip() {
System.out.println("[正社員] " + name + "(" + employeeId + ")");
System.out.println(" 基本給: " + baseSalary + "円");
System.out.println(" ボーナス: " + calculateBonus() + "円");
System.out.println(" 年収: " + calculateAnnualSalary() + "円");
}
}
class PartTimeEmployee extends Employee {
private int hourlyRate;
private int hoursWorked;
PartTimeEmployee(String name, String employeeId, int hourlyRate, int hoursWorked) {
super(name, employeeId, 0);
this.hourlyRate = hourlyRate;
this.hoursWorked = hoursWorked;
}
@Override
int calculateMonthlySalary() {
return hourlyRate * hoursWorked;
}
@Override
public void showPaySlip() {
System.out.println("[パート] " + name + "(" + employeeId + ")");
System.out.println(" 時給: " + hourlyRate + "円 × " + hoursWorked + "時間");
System.out.println(" 月給: " + calculateMonthlySalary() + "円");
}
}
class Manager extends FullTimeEmployee {
private int managementAllowance;
Manager(String name, String employeeId, int baseSalary, double bonusMultiplier, int managementAllowance) {
super(name, employeeId, baseSalary, bonusMultiplier);
this.managementAllowance = managementAllowance;
}
@Override
int calculateMonthlySalary() {
return baseSalary + managementAllowance;
}
@Override
int calculateAnnualSalary() {
return calculateMonthlySalary() * 12 + calculateBonus();
}
@Override
public void showPaySlip() {
System.out.println("[管理職] " + name + "(" + employeeId + ")");
System.out.println(" 基本給: " + baseSalary + "円");
System.out.println(" 管理手当: " + managementAllowance + "円");
System.out.println(" ボーナス: " + calculateBonus() + "円");
System.out.println(" 年収: " + calculateAnnualSalary() + "円");
}
}
public class Exercise10 {
public static void main(String[] args) {
Employee[] employees = {
new FullTimeEmployee("田中太郎", "E001", 300000, 2.0),
new PartTimeEmployee("鈴木花子", "E002", 1200, 120),
new Manager("佐藤部長", "E003", 400000, 2.0, 80000)
};
System.out.println("=== 給与明細一覧 ===");
int totalMonthlyCost = 0;
for (Employee e : employees) {
System.out.println();
e.showPaySlip();
totalMonthlyCost += e.calculateMonthlySalary();
}
System.out.println("\n--- 月間人件費合計 ---");
for (Employee e : employees) {
String type;
if (e instanceof Manager) {
type = "管理職";
} else if (e instanceof FullTimeEmployee) {
type = "正社員";
} else {
type = "パート";
}
System.out.println(type + ": " + e.calculateMonthlySalary() + "円");
}
System.out.println("合計: " + totalMonthlyCost + "円");
}
}
ポイント: 抽象クラス、インターフェース、継承、オーバーライド、ポリモーフィズムを組み合わせた総合問題です。Employee 型の配列でさまざまな種類の社員を統一的に扱えるのがポリモーフィズムの威力です。instanceof の判定順序に注意(Manager は FullTimeEmployee でもあるため、先に判定する必要があります)。
まとめ
| 問題 | テーマ | 難易度 |
|---|---|---|
| 問題1 | 基本的な継承 | ⭐ |
| 問題2 | メソッドのオーバーライド | ⭐ |
| 問題3 | super キーワード | ⭐⭐ |
| 問題4 | 抽象クラス | ⭐⭐ |
| 問題5 | インターフェース | ⭐⭐ |
| 問題6 | 複数インターフェースの実装 | ⭐⭐ |
| 問題7 | instanceof と型キャスト | ⭐⭐ |
| 問題8 | デフォルトメソッド | ⭐⭐⭐ |
| 問題9 | テンプレートメソッドパターン | ⭐⭐⭐ |
| 問題10 | 社員管理システム(総合問題) | ⭐⭐⭐ |
次回は 例外処理 のコーディング問題です!
シリーズ一覧:Java研修コーディング問題集
- 変数・データ型・演算子 編
- 条件分岐(if / switch)編
- 繰り返し処理(for / while)編
- 配列 編
- メソッド 編
- 文字列操作(String)編
- クラスとオブジェクト 編
- 👉 継承とインターフェース 編(本記事)
- 例外処理 編
- コレクションと Stream API 編
著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!