Java継承の基礎とメソッドオーバーライド

2025-08-01

はじめに

オブジェクト指向プログラミングの三大要素の一つである「継承」は、Javaプログラミングにおいて非常に重要な概念です。継承を理解することで、コードの再利用性が高まり、効率的なプログラム開発が可能になります。本記事では、継承の基本概念から、メソッドオーバーライドの実践的な活用方法まで、具体例を交えて詳細に解説します。

継承の基本概念

継承とは何か?

継承とは、既存のクラスを基にして新しいクラスを作成する仕組みです。現実世界の「親子関係」に例えると分かりやすいでしょう。

現実世界の例としては以下のようなことが考えられます。

  • 親:哺乳類
  • 子:犬、猫、人間

哺乳類という大きなカテゴリ(親クラス)の特徴を、犬や猫(子クラス)が受け継いでいるように、プログラミングでも同様の関係を構築できます。

継承の基本的な構文

次のコードは、継承の基本構文を示しています。親クラスを基にして子クラスが拡張されており、子クラス親クラスのフィールドやメソッドを引き継ぎつつ、自分専用の機能を追加できます。

class 親クラス {
    // 親クラスのフィールドとメソッド
}

class 子クラス extends 親クラス {
    // 子クラス独自のフィールドとメソッド
}

このように、extendsキーワードを使用して継承関係を表現します。

基本的な継承の例

実際のコードで継承を見てみましょう。このコードは、継承とメソッドのオーバーライドを用いたオブジェクト指向の基本構造を示しています。Animalクラスが親クラスとして共通の属性や動作(名前・年齢・食事・睡眠など)を持ち、DogCatクラスがそれを継承して独自の特徴(犬種や毛の種類)と行動(吠える、木に登るなど)を追加しています。また、displayInfoメソッドをオーバーライドして、親クラスの情報にそれぞれの追加情報を表示するように拡張しています。

/**
 * 動物を表す親クラス
 */
class Animal {
    // フィールド(属性)
    protected String name;
    protected int age;

    // コンストラクタ
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println(name + "が生まれました!");
    }

    // メソッド(動作)
    public void eat() {
        System.out.println(name + "が食事をしています。");
    }

    public void sleep() {
        System.out.println(name + "が眠っています。");
    }

    public void makeSound() {
        System.out.println(name + "が音を出しています。");
    }

    // 情報表示メソッド
    public void displayInfo() {
        System.out.println("名前: " + name + ", 年齢: " + age + "歳");
    }
}

/**
 * Animalクラスを継承したDogクラス
 */
class Dog extends Animal {
    private String breed; // 犬種(Dogクラス独自のフィールド)

    // コンストラクタ
    public Dog(String name, int age, String breed) {
        super(name, age); // 親クラスのコンストラクタを呼び出し
        this.breed = breed;
    }

    // Dogクラス独自のメソッド
    public void bark() {
        System.out.println(name + "がワンワンと吠えています!");
    }

    public void fetch() {
        System.out.println(name + "がボールを取ってきました!");
    }

    // 情報表示メソッドを拡張
    @Override
    public void displayInfo() {
        super.displayInfo(); // 親クラスのメソッドを呼び出し
        System.out.println("犬種: " + breed);
    }
}

/**
 * Animalクラスを継承したCatクラス
 */
class Cat extends Animal {
    private boolean hasLongHair; // 長毛種かどうか(Catクラス独自のフィールド)

    // コンストラクタ
    public Cat(String name, int age, boolean hasLongHair) {
        super(name, age); // 親クラスのコンストラクタを呼び出し
        this.hasLongHair = hasLongHair;
    }

    // Catクラス独自のメソッド
    public void purr() {
        System.out.println(name + "がゴロゴロと鳴いています。");
    }

    public void climbTree() {
        System.out.println(name + "が木に登りました!");
    }

    // 情報表示メソッドを拡張
    @Override
    public void displayInfo() {
        super.displayInfo(); // 親クラスのメソッドを呼び出し
        String hairType = hasLongHair ? "長毛種" : "短毛種";
        System.out.println("毛の種類: " + hairType);
    }
}

継承の利点

この例から分かる継承の利点は以下の通りです。

  1. コードの再利用性

eat()sleep()などの共通メソッドを親クラスで一度定義するだけで、全ての子クラスで利用可能

  1. 保守性の向上

共通の処理を親クラスで管理できるため、修正が必要な場合も一箇所で対応可能

  1. 階層的な設計

現実世界の概念を自然に表現できる

  1. 拡張性

新しい種類の動物を追加する際に、既存のコードを大幅に再利用できる

メソッドオーバーライドの詳細

メソッドオーバーライドとは?

メソッドオーバーライドとは、親クラスで定義されたメソッドを子クラスで再定義することを指します。これにより、子クラスで親クラスのメソッドの動作を変更または拡張できます。

オーバーライドの基本構文

このコードは、**メソッドのオーバーライド(override)**の基本構文を示しています。子クラス親クラスを継承し、同じ名前と引数を持つメソッドを再定義することで、処理内容を変更できます。また、必要に応じてsuper.メソッド名()を使えば、親クラスの元の処理を呼び出すことも可能です。

class 親クラス {
    public void メソッド名() {
        // 親クラスの実装
    }
}

class 子クラス extends 親クラス {
    @Override
    public void メソッド名() {
        // 子クラスでの新しい実装
        // 必要に応じて super.メソッド名() で親の実装を呼び出せる
    }
}

オーバーライドの具体例

先ほどのAnimalクラスを拡張して、オーバーライドの具体例を見てみましょう。このコードは、オーバーライドとfinalメソッドの使い分けを示した継承の応用例です。
AdvancedAnimalクラスが基本動作(鳴く・移動・呼吸)を定義し、BirdFishクラスがそれを継承して、それぞれに合った動作へとオーバーライドしています。
breatheメソッドにはfinal修飾子が付いており、子クラスで変更(オーバーライド)できません。
また、鳥はbuildNest、魚はswimといった独自のメソッドを追加し、動物の多様な行動を表現しています。

/**
 * 動物クラスの拡張例
 */
class AdvancedAnimal {
    protected String name;

    public AdvancedAnimal(String name) {
        this.name = name;
    }

    // オーバーライドされることを想定したメソッド
    public void makeSound() {
        System.out.println(name + "が何かの音を出しています。");
    }

    public void move() {
        System.out.println(name + "が移動しています。");
    }

    // finalメソッド - オーバーライドできない
    public final void breathe() {
        System.out.println(name + "が呼吸しています。");
    }
}

/**
 * 鳥クラス
 */
class Bird extends AdvancedAnimal {
    private double wingSpan; // 翼幅

    public Bird(String name, double wingSpan) {
        super(name);
        this.wingSpan = wingSpan;
    }

    // makeSoundメソッドをオーバーライド
    @Override
    public void makeSound() {
        System.out.println(name + "がチュンチュンと鳴いています!");
    }

    // moveメソッドをオーバーライド
    @Override
    public void move() {
        System.out.println(name + "が空を飛んでいます!翼幅: " + wingSpan + "cm");
    }

    // 鳥クラス独自のメソッド
    public void buildNest() {
        System.out.println(name + "が巣を作っています。");
    }
}

/**
 * 魚クラス
 */
class Fish extends AdvancedAnimal {
    private String waterType; // 淡水か海水か

    public Fish(String name, String waterType) {
        super(name);
        this.waterType = waterType;
    }

    // makeSoundメソッドをオーバーライド
    @Override
    public void makeSound() {
        System.out.println(name + "がブクブクと音を立てています。");
    }

    // moveメソッドをオーバーライド
    @Override
    public void move() {
        System.out.println(name + "が" + waterType + "を泳いでいます!");
    }

    // 魚クラス独自のメソッド
    public void swim() {
        System.out.println(name + "が優雅に泳いでいます。");
    }
}

オーバーライドのルール

メソッドオーバーライドには以下の重要なルールがあります。

  1. アクセス修飾子の制限

オーバーライドするメソッドのアクセスレベルを、親クラスのメソッドより厳しくすることはできない。

  1. 戻り値の型

戻り値の型は同じか、サブタイプである必要がある(共変戻り値)。

  1. メソッド名と引数

メソッド名と引数の型、数、順序が完全に一致している必要がある。

  1. finalメソッド

finalで宣言されたメソッドはオーバーライドできない。

  1. staticメソッド

staticメソッドはオーバーライドではなく「隠蔽」される。

  1. 例外の制限

オーバーライドするメソッドは、親クラスのメソッドよりも多くの検査例外を投げることはできない。

実践的な継承とオーバーライドの活用

実際のアプリケーションでの使用例

より実践的な例として、図形計算アプリケーションを考えてみましょう。図形(Shape)を表すクラス階層を具象クラスで実装したものです。
図形の基本情報と共通機能を持つ Shape クラスを親として、Circle(円)、Rectangle(四角形)、Square(正方形)をそれぞれ具体的に定義しています。

/**
 * 図形の基本クラス(具象クラスに変更)
 */
class Shape {
    protected String color;
    protected boolean filled;

    public Shape(String color, boolean filled) {
        this.color = color;
        this.filled = filled;
    }

    public double calculateArea() {
        return 0.0;
    }

    public double calculatePerimeter() {
        return 0.0;
    }

    // 共通機能
    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public boolean isFilled() {
        return filled;
    }

    public void setFilled(boolean filled) {
        this.filled = filled;
    }

    @Override
    public String toString() {
        return "図形 [色=" + color + ", 塗りつぶし=" + filled + "]";
    }
}

/**
 * 円クラス
 */
class Circle extends Shape {
    private double radius;

    public Circle(String color, boolean filled, double radius) {
        super(color, filled);
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return "円 [半径=" + radius + ", 色=" + color + ", 塗りつぶし=" + filled + "]";
    }
}

/**
 * 四角形クラス
 */
class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(String color, boolean filled, double width, double height) {
        super(color, filled);
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * (width + height);
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "四角形 [幅=" + width + ", 高さ=" + height + 
               ", 色=" + color + ", 塗りつぶし=" + filled + "]";
    }
}

/**
 * 正方形クラス
 */
class Square extends Rectangle {

    public Square(String color, boolean filled, double side) {
        super(color, filled, side, side);
    }

    public double getSide() {
        return getWidth();
    }

    public void setSide(double side) {
        setWidth(side);
        setHeight(side);
    }

    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(double height) {
        super.setWidth(height);
        super.setHeight(height);
    }

    @Override
    public String toString() {
        return "正方形 [一辺=" + getWidth() + ", 色=" + getColor() + 
               ", 塗りつぶし=" + isFilled() + "]";
    }
}

それぞれのクラスの関係性を以下に示します。

1. Shapeクラス(共通の基本クラス)

Shapeはすべての図形の共通要素を持つスーパークラスです。
抽象ではなく具象クラスなので、直接インスタンス化も可能です。

  • 共通フィールドcolor(色)、filled(塗りつぶしの有無)
  • デフォルト実装
    • calculateArea() → 面積(0.0を返す)
    • calculatePerimeter() → 周囲長(0.0を返す)
      抽象メソッドではないため、子クラスで上書きすることも、しないことも可能です。
  • 共通メソッド:getter/setter、toString() などを持ち、継承クラスで再利用できます。

2. Circleクラス(円)

Shapeを継承し、円の性質を具体的に実装しています。

  • 追加フィールドradius(半径)
  • メソッドのオーバーライド
    • 面積 = πr²
    • 周囲長 = 2πr
  • toString()を上書きし、円の半径や色の情報をわかりやすく出力します。

3. Rectangleクラス(四角形)

Shapeを継承した長方形クラスで、幅と高さを持ちます。

  • フィールドwidth(幅)、height(高さ)
  • メソッドのオーバーライド
    • 面積 = 幅 × 高さ
    • 周囲長 = 2 × (幅 + 高さ)
  • toString()で形状の詳細を出力。
    他のクラス(例:Square)に継承される基礎となります。

4. Squareクラス(正方形)

Rectangleを継承し、「幅と高さが常に同じ」ことを保証します。

  • コンストラクタ:一辺の長さを指定すると、幅・高さの両方に反映。
  • 幅・高さのセッターをオーバーライドし、どちらを設定しても常に同じ値になるよう制御。
  • **toString()**で正方形としての情報を出力。

高度なトピック

superキーワードの詳細

superキーワードは、親クラスのメンバーにアクセスするために使用されます。このコードは、Dogクラスをさらに拡張したサブクラスSmartDogの定義です。
trickフィールドで特技を追加し、displayInfoメソッドをオーバーライド
して犬種情報に加え特技も表示します。
また、barkメソッドではsuper.makeSound()を呼び出して親クラスの基本的な鳴き声動作を実行したうえで、「賢そうに吠える」という新たな振る舞いを加えています。
このように、親クラスの機能を再利用しつつ拡張するのが継承の利点です。

class SmartDog extends Dog {
    private String trick;

    public SmartDog(String name, int age, String breed, String trick) {
        super(name, age, breed); // 親クラスのコンストラクタ呼び出し
        this.trick = trick;
    }

    @Override
    public void displayInfo() {
        super.displayInfo(); // 親クラスのdisplayInfo()を呼び出し
        System.out.println("特技: " + trick);
    }

    // 親クラスのメソッドを拡張
    public void bark() {
        super.makeSound(); // 親クラスの基本動作
        System.out.println("そして、賢そうに吠えています!");
    }
}

コンストラクタと継承

コンストラクタは継承されませんが、子クラスのコンストラクタから親クラスのコンストラクタを呼び出す必要があります。ChildクラスはParentクラスを継承し、親クラスのコンストラクタをsuper(parentField)最初に明示的に呼び出しています
これにより、親クラスの初期化処理(Parentのフィールド設定など)が先に実行され、その後で子クラス独自のフィールドchildFieldが初期化されます。
実行時には、親→子の順にコンストラクタ呼び出しメッセージが表示され、継承における初期化の流れが理解できます。

class Parent {
    private String parentField;

    public Parent(String parentField) {
        this.parentField = parentField;
        System.out.println("Parentコンストラクタが呼び出されました");
    }
}

class Child extends Parent {
    private String childField;

    public Child(String parentField, String childField) {
        super(parentField); // 必ず最初に呼び出す
        this.childField = childField;
        System.out.println("Childコンストラクタが呼び出されました");
    }
}

まとめ

継承とメソッドオーバーライドはJavaのオブジェクト指向プログラミングの中核をなす概念であり、これらをマスターすることで、コードの再利用性を高め、保守性の高い設計を実現し、現実世界の関係を自然にモデル化するとともに、拡張性のあるアプリケーションを構築できるようになります。

初学者にとっては少し複雑に感じるかもしれませんが、実際にコードを書いて試してみることで、その威力と便利さを実感できるでしょう。まずは簡単なクラスから始めて、徐々に複雑な継承関係を構築していくことをお勧めします。

演習問題

初級問題(3問)

初級1: 基本的な継承

// 動物クラス
class Animal {
    public void makeSound() {
        System.out.println("動物の鳴き声");
    }
}

// 犬クラス(Animalを継承)
class Dog extends Animal {
    // ここを実装
}

問題: DogクラスでmakeSound()メソッドをオーバーライドして、「ワンワン」と出力するようにしてください。

初級2: コンストラクタの継承

class Vehicle {
    protected String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }

    public void start() {
        System.out.println(brand + "が発進します");
    }
}

class Car extends Vehicle {
    private int doors;

    // ここにコンストラクタを実装

    public void showInfo() {
        System.out.println(brand + "の車で、ドア数は" + doors + "です");
    }
}

問題: Carクラスのコンストラクタを完成させ、brandとdoorsを初期化できるようにしてください。

初級3: スーパークラスのメソッド呼び出し

class Shape {
    public void draw() {
        System.out.println("図形を描画します");
    }
}

class Circle extends Shape {
    @Override
    public void draw() {
        // 親クラスのdraw()を呼び出した後
        // 「円を描画します」と出力
    }
}

問題: Circleクラスのdraw()メソッドを完成させ、親クラスのメソッドを呼び出した後に独自の処理を追加してください。

中級問題(6問)

中級1: メソッドのオーバーライドとsuperの使用

class Employee {
    protected String name;
    protected double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public double calculateBonus() {
        return salary * 0.1;
    }

    public void displayInfo() {
        System.out.println("名前: " + name + ", 給与: " + salary);
    }
}

class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    // calculateBonus()をオーバーライド
    // 基本ボーナス(親クラスのメソッド使用)+追加ボーナス
}

問題: ManagerクラスのcalculateBonus()メソッドをオーバーライドし、基本ボーナスに追加ボーナスを加算して返すようにしてください。

中級2: 複数レベルの継承

class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void introduce() {
        System.out.println("私は" + name + "です。" + age + "歳です。");
    }
}

class Student extends Person {
    protected String studentId;

    public Student(String name, int age, String studentId) {
        super(name, age);
        this.studentId = studentId;
    }

    @Override
    public void introduce() {
        // 親クラスのintroduce()を呼び出し
        // 学籍番号も表示
    }
}

class GraduateStudent extends Student {
    private String researchTheme;

    public GraduateStudent(String name, int age, String studentId, String researchTheme) {
        super(name, age, studentId);
        this.researchTheme = researchTheme;
    }

    @Override
    public void introduce() {
        // 研究テーマも含めて自己紹介
    }
}

問題: StudentクラスとGraduateStudentクラスのintroduce()メソッドをオーバーライドして、適切な自己紹介ができるようにしてください。

中級3: クラスと継承

class BankAccount {
    protected String accountNumber;
    protected double balance;

    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    // 引き出し処理を記述してください
    public void withdraw(double amount) {
        // ここに処理を記述
    }

    public void deposit(double amount) {
        balance += amount;
        System.out.println(amount + "円入金しました。残高: " + balance);
    }

    public void displayBalance() {
        System.out.println("口座番号: " + accountNumber + ", 残高: " + balance);
    }
}

class SavingsAccount extends BankAccount {
    private static final double WITHDRAWAL_LIMIT = 50000;

    public SavingsAccount(String accountNumber, double initialBalance) {
        super(accountNumber, initialBalance);
    }

    // withdrawメソッドを上書きして実装してください
    // 引出限度額を超える場合はエラーメッセージを表示し、
    // 残高不足の場合もエラーを出すようにしてください
}

問題: SavingsAccountクラスのwithdraw()メソッドを実装し、引出限度額のチェックを行うようにしてください。

中級4: インターフェースの実装

interface Playable {
    void play();
    void stop();
}

class MusicPlayer implements Playable {
    // インターフェースのメソッドを実装
}

class VideoPlayer implements Playable {
    // インターフェースのメソッドを実装
}

問題: Playableインターフェースを実装するMusicPlayerクラスとVideoPlayerクラスを作成し、それぞれのメソッドを実装してください。

中級5: オーバーライドとオーバーロード

class Animal {
    public void makeSound() {
        System.out.println("動物の鳴き声");
    }

    // オーバーロード:引数付きバージョンを定義してください
    public void makeSound(String name) {
        // ここに処理を記述
    }
}

class Cat extends Animal {
    // オーバーライド:親クラスの動作を変更してください
    @Override
    public void makeSound() {
        // ここに処理を記述
    }

    // オーバーロード:同名メソッドを別パターンで定義してください
    public void makeSound(int times) {
        // ここに処理を記述(回数分鳴くように)
    }
}

public class OverrideOverloadTest {
    public static void main(String[] args) {
        Cat cat = new Cat();

        // オーバーライドされたメソッドを呼び出す
        cat.makeSound(); 

        // オーバーロードされたメソッド(引数付き)を呼び出す
        cat.makeSound("タマ");

        // オーバーロードされたメソッド(回数指定)を呼び出す
        cat.makeSound(3);
    }
}

問題: オーバーライドを利用して親クラスAnimalmakeSound()を子クラスCatで上書きしてください。オーバーロードを利用して同じメソッド名で引数違い(Stringint)を定義してください。

中級6: オーバーライドと例外処理

class Base {
    public void process() throws IOException {
        // 何らかの処理
    }
}

class Derived extends Base {
    @Override
    public void process() {
        // オーバーライドして、IOExceptionをスローしない
        // ファイル処理のシミュレーション
    }
}

問題: Derivedクラスのprocess()メソッドをオーバーライドし、IOExceptionをスローしないように修正してください。また、ファイル処理の成功メッセージを出力するように実装してください。

上級問題(3問)

上級1: テンプレートメソッドパターン

class DataProcessor {
    // 一連の処理を定義(テンプレート的な流れ)
    public final void process() {
        readData();
        transformData();
        saveData();
    }

    // データを読み込む処理を記述してください
    protected void readData() {
        // ここに処理を記述
    }

    // データを変換する処理を記述してください
    protected void transformData() {
        // ここに処理を記述
    }

    // データを保存する処理(共通処理)
    protected void saveData() {
        System.out.println("データを保存します");
    }
}

class CSVProcessor extends DataProcessor {
    // readData() と transformData() をCSV用に上書きしてください
}

class XMLProcessor extends DataProcessor {
    // readData() と transformData() をXML用に上書きしてください
}

問題: CSVProcessorクラスとXMLProcessorクラスを完成させ、それぞれのデータ処理方法を実装してください。

上級2: 深い継承階層とデザインパターン

class Notification {
    public void send() {
        System.out.println("通知を送信します");
    }
}

class EmailNotification extends Notification {
    @Override
    public void send() {
        // メール送信の前処理
        super.send();
        // メール送信の後処理
    }
}

class SMSNotification extends Notification {
    @Override
    public void send() {
        // SMS送信の特別な処理
    }
}

// さらに拡張したクラスを作成
class PriorityEmailNotification extends EmailNotification {
    @Override
    public void send() {
        // 優先メールの特別な処理を追加
    }
}

問題: 各通知クラスのsend()メソッドを適切にオーバーライドし、以下の機能を実現してください。

  • EmailNotification: 前処理で「メールを準備します」、後処理で「メールを送信しました」と出力
  • SMSNotification: 「SMSを送信します」と出力
  • PriorityEmailNotification: 「優先メールとして処理します」と出力した後、親クラスの処理を実行

上級3: 複雑なクラス設計

// 既存のクラス
class Payment {
    protected double amount;

    public Payment(double amount) {
        this.amount = amount;
    }

    public void processPayment() {
        System.out.println("支払い処理: " + amount + "円");
    }

    public double calculateFee() {
        return amount * 0.02;
    }
}

// 新しい要件に対応するクラス設計
// 1. クレジットカード支払い(手数料3%)
// 2. 銀行振込支払い(固定手数料300円)
// 3. ポイントを使用した支払い(手数料無料)

問題: 上記の要件を満たすクラス設計を作成し、各支払い方法を実装してください。必要に応じてクラスを追加し、オーバーライドを適切に使用してください。

演習問題解答例

初級問題 解答例

初級1: 基本的な継承

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("ワンワン");
    }
}

初級2: コンストラクタの継承

class Car extends Vehicle {
    private int doors;

    public Car(String brand, int doors) {
        super(brand);  // 親クラスのコンストラクタを呼び出し
        this.doors = doors;
    }

    public void showInfo() {
        System.out.println(brand + "の車で、ドア数は" + doors + "です");
    }
}

初級3: スーパークラスのメソッド呼び出し

class Circle extends Shape {
    @Override
    public void draw() {
        super.draw();  // 親クラスのメソッドを呼び出し
        System.out.println("円を描画します");
    }
}

中級問題 解答例

中級1: メソッドのオーバーライドとsuperの使用

class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    @Override
    public double calculateBonus() {
        double baseBonus = super.calculateBonus();  // 親クラスのボーナス計算
        return baseBonus + bonus;
    }

    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("役職: マネージャー, 追加ボーナス: " + bonus);
    }
}

中級2: 複数レベルの継承

class Student extends Person {
    protected String studentId;

    public Student(String name, int age, String studentId) {
        super(name, age);
        this.studentId = studentId;
    }

    @Override
    public void introduce() {
        super.introduce();  // 親クラスの自己紹介
        System.out.println("学籍番号は" + studentId + "です");
    }
}

class GraduateStudent extends Student {
    private String researchTheme;

    public GraduateStudent(String name, int age, String studentId, String researchTheme) {
        super(name, age, studentId);
        this.researchTheme = researchTheme;
    }

    @Override
    public void introduce() {
        super.introduce();  // Studentクラスの自己紹介
        System.out.println("研究テーマは「" + researchTheme + "」です");
    }
}

中級3: クラスと継承

class BankAccount {
    protected String accountNumber;
    protected double balance;

    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    // 基本的な引き出し処理
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("引き出し金額は正の数で指定してください。");
            return;
        }

        if (amount > balance) {
            System.out.println("残高不足です。引き出しできません。");
        } else {
            balance -= amount;
            System.out.println(amount + "円引き出しました。残高: " + balance);
        }
    }

    public void deposit(double amount) {
        balance += amount;
        System.out.println(amount + "円入金しました。残高: " + balance);
    }

    public void displayBalance() {
        System.out.println("口座番号: " + accountNumber + ", 残高: " + balance);
    }
}

class SavingsAccount extends BankAccount {
    private static final double WITHDRAWAL_LIMIT = 50000;

    public SavingsAccount(String accountNumber, double initialBalance) {
        super(accountNumber, initialBalance);
    }

    // 引き出し制限付きの処理をオーバーライド
    @Override
    public void withdraw(double amount) {
        if (amount > WITHDRAWAL_LIMIT) {
            System.out.println("1回の引き出しは" + WITHDRAWAL_LIMIT + "円までです。");
        } else if (amount > balance) {
            System.out.println("残高不足です。引き出しできません。");
        } else if (amount <= 0) {
            System.out.println("引き出し金額は正の数で指定してください。");
        } else {
            balance -= amount;
            System.out.println(amount + "円引き出しました。残高: " + balance);
        }
    }
}

中級4: インターフェースの実装

class MusicPlayer implements Playable {
    @Override
    public void play() {
        System.out.println("音楽を再生します");
    }

    @Override
    public void stop() {
        System.out.println("音楽を停止します");
    }
}

class VideoPlayer implements Playable {
    @Override
    public void play() {
        System.out.println("動画を再生します");
    }

    @Override
    public void stop() {
        System.out.println("動画を停止します");
    }
}

中級5: オーバーライドとオーバーロード

class Animal {
    public void makeSound() {
        System.out.println("動物の鳴き声");
    }

    // オーバーロード:引数付きバージョン
    public void makeSound(String name) {
        System.out.println(name + "が鳴いています");
    }
}

class Cat extends Animal {
    // オーバーライド:親クラスの動作を変更
    @Override
    public void makeSound() {
        System.out.println("ニャー");
    }

    // オーバーロード:同名メソッドを別パターンで定義
    public void makeSound(int times) {
        for (int i = 0; i < times; i++) {
            System.out.println("ニャー");
        }
    }
}

public class OverrideOverloadTest {
    public static void main(String[] args) {
        Cat cat = new Cat();

        // オーバーライドされたメソッドを呼び出し
        cat.makeSound(); 

        // オーバーロードされたメソッド(引数付き)
        cat.makeSound("タマ");

        // オーバーロードされたメソッド(回数指定)
        cat.makeSound(3);
    }
}

実行結果:

動物の鳴き声
ニャー
チュンチュン

中級6: オーバーライドと例外処理

import java.io.*;

class Derived extends Base {
    @Override
    public void process() {
        try {
            // ファイル処理のシミュレーション
            System.out.println("ファイル処理を開始します");
            // 実際のファイル処理コード...
            System.out.println("ファイル処理が成功しました");
        } catch (Exception e) {
            System.out.println("処理中にエラーが発生しました: " + e.getMessage());
        }
    }
}

上級問題 解答例

上級1: テンプレートメソッドパターン

class DataProcessor {
    public final void process() {
        readData();
        transformData();
        saveData();
    }

    protected void readData() {
        System.out.println("データを読み込みます(基本処理)");
    }

    protected void transformData() {
        System.out.println("データを変換します(基本処理)");
    }

    protected void saveData() {
        System.out.println("データを保存します");
    }
}

class CSVProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("CSVファイルを読み込みます");
    }

    @Override
    protected void transformData() {
        System.out.println("CSVデータを整形して変換します");
    }
}

class XMLProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("XMLファイルを読み込みます");
    }

    @Override
    protected void transformData() {
        System.out.println("XMLデータを構造化して変換します");
    }
}

上級2: 深い継承階層とデザインパターン

class EmailNotification extends Notification {
    @Override
    public void send() {
        System.out.println("メールを準備します");
        super.send();  // 親クラスの基本処理
        System.out.println("メールを送信しました");
    }
}

class SMSNotification extends Notification {
    @Override
    public void send() {
        System.out.println("SMSを送信します");
        // 実際のSMS送信処理...
    }
}

class PriorityEmailNotification extends EmailNotification {
    @Override
    public void send() {
        System.out.println("優先メールとして処理します");
        super.send();  // EmailNotificationのsend()を呼び出し
    }
}

上級3: 複雑なクラス設計

// クレジットカード支払いクラス
class CreditCardPayment extends Payment {
    public CreditCardPayment(double amount) {
        super(amount);
    }

    @Override
    public void processPayment() {
        System.out.println("クレジットカードで支払い処理: " + amount + "円");
    }

    @Override
    public double calculateFee() {
        return amount * 0.03;  // 手数料3%
    }
}

// 銀行振込支払いクラス
class BankTransferPayment extends Payment {
    private static final double FIXED_FEE = 300;

    public BankTransferPayment(double amount) {
        super(amount);
    }

    @Override
    public void processPayment() {
        System.out.println("銀行振込で支払い処理: " + amount + "円");
    }

    @Override
    public double calculateFee() {
        return FIXED_FEE;  // 固定手数料300円
    }
}

// ポイント支払いクラス
class PointPayment extends Payment {
    private int points;

    public PointPayment(double amount, int points) {
        super(amount);
        this.points = points;
    }

    @Override
    public void processPayment() {
        System.out.println("ポイントで支払い処理: " + amount + "円");
        System.out.println("使用ポイント: " + points + "ポイント");
    }

    @Override
    public double calculateFee() {
        return 0;  // 手数料無料
    }

    public int getRemainingPoints() {
        return points - (int)amount;  // 簡易的なポイント計算
    }
}

// 使用例
public class PaymentTest {
    public static void main(String[] args) {
        Payment[] payments = new Payment[3];
        payments[0] = new CreditCardPayment(10000);
        payments[1] = new BankTransferPayment(5000);
        payments[2] = new PointPayment(3000, 5000);

        for (Payment payment : payments) {
            payment.processPayment();
            System.out.println("手数料: " + payment.calculateFee() + "円");
            System.out.println("---");
        }
    }
}

テスト用メインクラスの例

public class Main {
    public static void main(String[] args) {
        // 初級1のテスト
        Dog dog = new Dog();
        dog.makeSound();  // ワンワン

        // 中級2のテスト
        GraduateStudent gradStudent = new GraduateStudent("山田太郎", 24, "2024001", "機械学習");
        gradStudent.introduce();

        // 中級5のテスト
        Animal[] animals = {new Animal(), new Dog(), new Cat()};
        for (Animal animal : animals) {
            animal.makeSound();
        }

        // 上級3のテスト
        Payment creditPayment = new CreditCardPayment(10000);
        creditPayment.processPayment();
        System.out.println("クレジットカード手数料: " + creditPayment.calculateFee());
    }
}