10日で覚えるJavaDay 7: 継承とポリモーフィズム
books.chapter 710日で覚えるJava

Day 7: 継承とポリモーフィズム

今日学ぶこと

  • 継承(extends)
  • メソッドのオーバーライド
  • 抽象クラス
  • インターフェース
  • ポリモーフィズム
  • sealed クラス(Java 17+)

継承

既存のクラスを拡張して新しいクラスを作る仕組みです。

// 親クラス(スーパークラス)
public class Animal {
    protected String name;

    Animal(String name) {
        this.name = name;
    }

    void speak() {
        System.out.println(name + ": ...");
    }
}

// 子クラス(サブクラス)
public class Dog extends Animal {
    private String breed;

    Dog(String name, String breed) {
        super(name); // 親のコンストラクタを呼ぶ
        this.breed = breed;
    }

    @Override
    void speak() {
        System.out.println(name + ": ワン!");
    }

    void fetch() {
        System.out.println(name + "がボールを取ってきた!");
    }
}
flowchart TB
    Animal["Animal<br>name, speak()"]
    Dog["Dog<br>breed, fetch()"]
    Cat["Cat<br>indoor"]
    Animal --> Dog
    Animal --> Cat
    style Animal fill:#3b82f6,color:#fff
    style Dog fill:#22c55e,color:#fff
    style Cat fill:#22c55e,color:#fff
用語 説明
extends クラスの継承
super 親クラスへの参照
@Override メソッドの上書き
protected サブクラスからアクセス可能

制約: Javaは単一継承です。1つのクラスしかextendsできません。


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

public class Cat extends Animal {
    Cat(String name) {
        super(name);
    }

    @Override
    void speak() {
        System.out.println(name + ": ニャー!");
    }
}

// 使用
Animal dog = new Dog("ポチ", "柴犬");
Animal cat = new Cat("ミケ");

dog.speak(); // ポチ: ワン!
cat.speak(); // ミケ: ニャー!

オーバーライドのルール

ルール 説明
メソッド名 同じ
引数 同じ
戻り値 同じまたはサブタイプ
アクセス修飾子 同じまたはより広く
@Override 付けることを推奨

抽象クラス

インスタンス化できないクラスです。サブクラスに実装を強制します。

abstract class Shape {
    String color;

    Shape(String color) {
        this.color = color;
    }

    // 抽象メソッド(実装なし)
    abstract double area();
    abstract double perimeter();

    // 具象メソッド(実装あり)
    void display() {
        System.out.printf("%s: 面積=%.2f, 周長=%.2f%n",
            color, area(), perimeter());
    }
}

class Circle extends Shape {
    double radius;

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

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

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

class Rectangle extends Shape {
    double width, height;

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

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

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

使い分け: 抽象クラスは**共通の状態(フィールド)**を持つ場合に使います。


インターフェース

クラスが実装すべき契約を定義します。

interface Printable {
    void print(); // 抽象メソッド
}

interface Savable {
    void save(String filename);

    // デフォルトメソッド(Java 8+)
    default void autoSave() {
        save("auto_save.dat");
    }
}

// 複数のインターフェースを実装
class Document implements Printable, Savable {
    private String content;

    Document(String content) {
        this.content = content;
    }

    @Override
    public void print() {
        System.out.println("印刷: " + content);
    }

    @Override
    public void save(String filename) {
        System.out.println(filename + " に保存: " + content);
    }
}
flowchart TB
    Printable["«interface»<br>Printable<br>print()"]
    Savable["«interface»<br>Savable<br>save(), autoSave()"]
    Document["Document<br>content"]
    Printable --> Document
    Savable --> Document
    style Printable fill:#8b5cf6,color:#fff
    style Savable fill:#8b5cf6,color:#fff
    style Document fill:#22c55e,color:#fff

抽象クラス vs インターフェース

特徴 抽象クラス インターフェース
継承 1つだけ 複数可能
フィールド あり 定数のみ
コンストラクタ あり なし
デフォルトメソッド あり あり(Java 8+)
用途 共通の状態と振る舞い 契約の定義

ポリモーフィズム

同じ型の変数で異なる実装を呼び出せる仕組みです。

Shape[] shapes = {
    new Circle("赤", 5),
    new Rectangle("青", 4, 6),
    new Circle("緑", 3)
};

for (Shape shape : shapes) {
    shape.display(); // 各サブクラスのメソッドが呼ばれる
}

instanceof とパターンマッチング(Java 16+)

// 従来の書き方
if (shape instanceof Circle) {
    Circle c = (Circle) shape;
    System.out.println("半径: " + c.radius);
}

// パターンマッチング(Java 16+)
if (shape instanceof Circle c) {
    System.out.println("半径: " + c.radius);
}

// switch式でのパターンマッチング(Java 21+)
String info = switch (shape) {
    case Circle c -> "円(半径: " + c.radius + ")";
    case Rectangle r -> "長方形(" + r.width + " × " + r.height + ")";
    default -> "不明な図形";
};

sealed クラス(Java 17+)

継承できるクラスを制限します。

sealed abstract class Payment permits CreditCard, BankTransfer, Cash {
    abstract double amount();
}

final class CreditCard extends Payment {
    private double amount;
    private String cardNumber;

    CreditCard(double amount, String cardNumber) {
        this.amount = amount;
        this.cardNumber = cardNumber;
    }

    @Override
    double amount() { return amount; }
}

final class BankTransfer extends Payment {
    private double amount;

    BankTransfer(double amount) {
        this.amount = amount;
    }

    @Override
    double amount() { return amount; }
}

final class Cash extends Payment {
    private double amount;

    Cash(double amount) {
        this.amount = amount;
    }

    @Override
    double amount() { return amount; }
}
修飾子 説明
sealed 許可されたサブクラスのみ継承可能
permits 継承を許可するクラスの一覧
final これ以上継承不可
non-sealed 制限を解除

実践: 動物園シミュレーション

interface Feedable {
    String favoriteFood();
}

abstract class Animal {
    protected String name;
    protected int age;

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

    abstract String speak();

    @Override
    public String toString() {
        return String.format("%s(%d歳)", name, age);
    }
}

class Dog extends Animal implements Feedable {
    Dog(String name, int age) { super(name, age); }

    @Override
    String speak() { return "ワン!"; }

    @Override
    public String favoriteFood() { return "骨"; }
}

class Cat extends Animal implements Feedable {
    Cat(String name, int age) { super(name, age); }

    @Override
    String speak() { return "ニャー!"; }

    @Override
    public String favoriteFood() { return "魚"; }
}

class Parrot extends Animal implements Feedable {
    private String phrase;

    Parrot(String name, int age, String phrase) {
        super(name, age);
        this.phrase = phrase;
    }

    @Override
    String speak() { return phrase; }

    @Override
    public String favoriteFood() { return "種"; }
}

public class Zoo {
    public static void main(String[] args) {
        Animal[] animals = {
            new Dog("ポチ", 3),
            new Cat("ミケ", 5),
            new Parrot("ピーちゃん", 2, "こんにちは!")
        };

        System.out.println("=== 動物園 ===");
        for (Animal animal : animals) {
            System.out.printf("%s → %s", animal, animal.speak());
            if (animal instanceof Feedable f) {
                System.out.printf(" [好物: %s]", f.favoriteFood());
            }
            System.out.println();
        }
    }
}

まとめ

概念 説明
継承 extendsで既存クラスを拡張
super 親クラスのコンストラクタ・メソッド呼び出し
@Override メソッドの上書き
抽象クラス インスタンス化不可、サブクラスに実装を強制
インターフェース 複数実装可能な契約
ポリモーフィズム 同じ型で異なる振る舞い
sealed 継承先を制限(Java 17+)
パターンマッチング instanceofの簡潔な記述(Java 16+)

重要ポイント

  1. Javaは単一継承extendsは1つだけ)
  2. インターフェースは複数実装可能
  3. ポリモーフィズムでコードの柔軟性を高める
  4. **sealed**で継承を安全に制限

練習問題

問題1: 基本

Vehicleクラスを親として、CarBicycleを作成し、それぞれのtoString()をオーバーライドしてください。

問題2: 応用

Comparableインターフェースを実装したStudentクラスを作成し、成績でソートできるようにしてください。

チャレンジ問題

sealedクラスを使って、図形の面積計算をパターンマッチング(switch式)で実装してください。Circle, Rectangle, Triangleをサポートし、それぞれの面積を返しましょう。


参考リンク


次回予告: Day 8では「例外処理とファイルI/O」について学びます。エラーハンドリングの仕組みをマスターしましょう。