Java / LESSON 10 / JV10

Java:継承

1学習の目的

2基礎解説

「本」「家具」「日用品」——どれも商品ですが、送料の計算だけが違うとします。3つのクラスを別々に書くと、名前や価格の処理を3回書くことになります。共通部分を親にまとめ、違いだけを子に書くのが継承です。

用語意味書き方
継承する親の機能を引き継ぐclass Book extends Product
親を呼ぶ親のコンストラクタsuper(name, price);
上書き親のメソッドを変更@Override
子クラスに公開親のフィールドprotected
✅ 覚えるべき重要ポイント
  1. extends で親の機能をすべて引き継ぐ。子クラスには違う部分だけを書けばよく、共通処理は親に1回書くだけで済む。
  2. 子のコンストラクタでは super(…) を最初に呼ぶ。親の初期化を先に済ませてから、子の独自フィールドを設定する。
  3. @Override で親のメソッドを上書きする。この注釈は必須ではないが、綴りを間違えたときにエラーで教えてくれるので必ず書く。
  4. ⭐ 親の型で扱っても、実際の動きは子のもの(ポリモーフィズム)。Product[] items に本も家具も入れられ、shippingFee() はそれぞれの実装が呼ばれる。
  5. 親のフィールドを子から使うなら protected。private だと子クラスからも見えない。

現場使用例:商品の種類別処理、社員と管理職、図形(円・四角)の面積計算、支払い方法の違い。

// 親クラス(共通部分)
static class Product {
    protected String name;      // 子から使えるように protected
    protected int price;

    Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    // 子で上書きされる想定のメソッド
    int shippingFee() {
        return 600;             // 標準の送料
    }

    @Override
    public String toString() {
        return name + ":" + price + "円(送料" + shippingFee() + "円)";
    }
}

// 子クラス①(本は送料無料)
static class Book extends Product {
    private String author;      // 子だけが持つフィールド

    Book(String name, int price, String author) {
        super(name, price);     // ① まず親を初期化
        this.author = author;   // ② 次に自分の分
    }

    @Override
    int shippingFee() {
        return 0;               // 上書き
    }
}

// 子クラス②(家具は送料が高い)
static class Furniture extends Product {
    Furniture(String name, int price) {
        super(name, price);
    }

    @Override
    int shippingFee() {
        return 3000;
    }
}

// ⭐ ポリモーフィズム:親の型でまとめて扱える
Product[] items = {
    new Product("マウス", 3200),
    new Book("Java入門", 2800, "山田"),
    new Furniture("デスク", 45000),
};

for (Product item : items) {
    System.out.println(item.shippingFee());   // 600 / 0 / 3000
}

// 子の固有機能を使いたいとき
if (item instanceof Book bk) {       // Java 16以降の書き方
    System.out.println(bk.getAuthor());
}

3基本ドリル(10問)

JV10-D01 Product クラスを継承した Book クラスを作れ(extends を使い、super で親を初期化)。本の名前を出力すること。 コーディング★☆☆無料
期待される結果

出力 == Java入門

ヒント

class Book extends Product { Book(...) { super(...); } } の形。

模範解答
public class Main {

    static class Product {
        protected String name;

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

    static class Book extends Product {
        Book(String name) {
            super(name);
        }
    }

    public static void main(String[] args) {
        Book b = new Book("Java入門");

        System.out.println(b.name);
    }
}
解説

Book は Product の機能をすべて引き継ぐ。name フィールドを自分で宣言していないのに使えるのは、親から受け継いでいるため。

JV10-D02 継承を表すキーワードはどれか。
A. implements B. extends C. inherits D. super
選択★☆☆無料
期待される結果

解答 == B

ヒント

「拡張する」を意味する英単語。

模範解答
B
解説

extends は「拡張する」という意味。親の機能に、子が独自の機能を足していくイメージ。

JV10-D03 親の shippingFee()(600を返す)を、子クラスで0を返すように上書きせよ(@Override を付けること)。子の結果を出力すること。 コーディング★☆☆無料
期待される結果

出力 == 0

ヒント

@Override を付けて同じ名前・同じ引数のメソッドを定義する。

模範解答
public class Main {

    static class Product {
        int shippingFee() {
            return 600;
        }
    }

    static class Book extends Product {
        @Override
        int shippingFee() {
            return 0;
        }
    }

    public static void main(String[] args) {
        Book b = new Book();

        System.out.println(b.shippingFee());
    }
}
解説

子の実装が優先される。親のメソッドは残っているが、子で上書きしたほうが呼ばれる。

JV10-D04 親のコンストラクタを呼ぶキーワードを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
public class Main {

    static class Parent {
        String name;
        Parent(String name) { this.name = name; }
    }

    static class Child extends Parent {
        Child(String name) {
            ____(name);
        }
    }

    public static void main(String[] args) {
        System.out.println(new Child("テスト").name);
    }
}
期待される結果

出力 == テスト

ヒント

「上位の・親の」を意味する5文字。

模範解答
super
解説

super は子のコンストラクタの最初に書く。親の初期化が済んでいないと、子の処理が正しく動かない可能性があるため。

JV10-D05 親のフィールドを子クラスから使いたいとき、適切な修飾子はどれか。
A. private B. protected C. static D. final
選択★☆☆無料
期待される結果

解答 == B

ヒント

private だと子からも見えない。

模範解答
B
解説

protected は「子クラスと同じパッケージ」から見える。private だと親のクラス内だけなので、子からアクセスできない。

JV10-D06 ポリモーフィズムを確認せよ。Product・Book・Furniture を作り、Product の配列に入れて送料をそれぞれ出力すること(600 / 0 / 3000)。 コーディング★★☆広告解放
期待される結果

出力3行。600 / 0 / 3000

ヒント

Product[] に子クラスのオブジェクトも入れられる。呼ばれるのは実際の型のメソッド。

模範解答
public class Main {

    static class Product {
        int shippingFee() { return 600; }
    }

    static class Book extends Product {
        @Override
        int shippingFee() { return 0; }
    }

    static class Furniture extends Product {
        @Override
        int shippingFee() { return 3000; }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product(),
            new Book(),
            new Furniture(),
        };

        for (Product item : items) {
            System.out.println(item.shippingFee());
        }
    }
}
解説

変数の型は Product なのに、動きは実際の型のもの。これがポリモーフィズム——同じ呼び出しで、対象に応じた処理が行われる。

JV10-D07 メソッドの上書きを示す注釈を補ってコードを完成させよ。 穴埋め★★☆広告解放
コード
public class Main {

    static class Parent {
        String greet() { return "親"; }
    }

    static class Child extends Parent {
        ____
        String greet() { return "子"; }
    }

    public static void main(String[] args) {
        System.out.println(new Child().greet());
    }
}
期待される結果

出力 == 子

ヒント

アットマークで始まる9文字の注釈。

模範解答
@Override
解説

@Override は必須ではないが、必ず書くべき。メソッド名を間違えたとき、「親にそんなメソッドは無い」とコンパイラが教えてくれる。

JV10-D08 Product p = new Book(); のとき p.shippingFee() はどちらが呼ばれるか。
A. Product のもの B. Book のもの C. エラー D. 両方
選択★★☆広告解放
期待される結果

解答 == B

ヒント

変数の型ではなく、実際のオブジェクトの型で決まる。

模範解答
B
解説

実行時の実際の型で決まる。変数の型(Product)は「何を入れられるか」を決めるだけで、動きは中身(Book)が決める。

JV10-D09 instanceof を使い、Product 配列の中から Book だけを見つけて著者名を出力せよ(Java 16以降の書き方 instanceof Book b を使うこと)。 コーディング★★☆広告解放
期待される結果

出力 == 山田

ヒント

if (item instanceof Book b) { b.getAuthor() } と書ける。

模範解答
public class Main {

    static class Product {
    }

    static class Book extends Product {
        private String author;

        Book(String author) {
            this.author = author;
        }

        String getAuthor() {
            return author;
        }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product(),
            new Book("山田"),
        };

        for (Product item : items) {
            if (item instanceof Book b) {
                System.out.println(b.getAuthor());
            }
        }
    }
}
解説

判定と変換が1行で書ける(Java 16以降)。従来は ((Book) item).getAuthor() とキャストが必要だった。

JV10-D10 親の toString() の中から、子で上書きされたメソッドを呼ばせよ(Book の送料0が反映されることを確認)。 コーディング★★☆広告解放
期待される結果

出力 == Java入門(送料0円)

ヒント

親の toString の中で shippingFee() を呼ぶと、子の実装が使われる。

模範解答
public class Main {

    static class Product {
        protected String name;

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

        int shippingFee() {
            return 600;
        }

        @Override
        public String toString() {
            return name + "(送料" + shippingFee() + "円)";
        }
    }

    static class Book extends Product {
        Book(String name) {
            super(name);
        }

        @Override
        int shippingFee() {
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println(new Book("Java入門"));
    }
}
解説

親のコードが、子の実装を呼んでいる。親を書いた時点では Book の存在すら知らないのに、正しく動く——これが継承の強力さ。

4実践シナリオ(5問)

JV10-S01 【社員と管理職】Employee(name・salary)を継承した Manager を作れ(管理職手当5万円を加算)。両方の月給を2行出力すること(一般30万、管理職30万+5万)。 コーディング★★☆広告解放
期待される結果

出力2行。300000 / 350000

ヒント

親に salary() を定義し、子で @Override して手当を足す。

模範解答
public class Main {

    static class Employee {
        protected String name;
        protected int baseSalary;

        Employee(String name, int baseSalary) {
            this.name = name;
            this.baseSalary = baseSalary;
        }

        int salary() {
            return baseSalary;
        }
    }

    static class Manager extends Employee {
        Manager(String name, int baseSalary) {
            super(name, baseSalary);
        }

        @Override
        int salary() {
            return baseSalary + 50000;
        }
    }

    public static void main(String[] args) {
        Employee e = new Employee("山田", 300000);
        Manager m = new Manager("田中", 300000);

        System.out.println(e.salary());
        System.out.println(m.salary());
    }
}
解説

基本給の管理は親、手当の計算は子。役職が増えても、親を変えずに新しい子クラスを足すだけで対応できる。

JV10-S02 【図形の面積】Shape を継承した Circle(半径3)と Rectangle(4×5)を作り、Shape の配列で面積を2行出力せよ(円は小数第1位、四角は整数)。 コーディング★★☆広告解放
期待される結果

出力2行。28.3 / 20.0

ヒント

親に double area() を定義し、子で計算方法を変える。円は Math.PI * r * r。

模範解答
public class Main {

    static class Shape {
        double area() {
            return 0;
        }
    }

    static class Circle extends Shape {
        private double radius;

        Circle(double radius) {
            this.radius = radius;
        }

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

    static 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;
        }
    }

    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle(3),
            new Rectangle(4, 5),
        };

        for (Shape s : shapes) {
            System.out.println(String.format("%.1f", s.area()));
        }
    }
}
解説

同じ area() の呼び出しで、円と四角が別々の計算をする。呼ぶ側は図形の種類を気にしなくてよい。

JV10-S03 【送料の合計】Product・Book・Furniture の3件を配列に入れ、送料の合計を出力せよ(600 + 0 + 3000)。 コーディング★★☆広告解放
期待される結果

出力 == 3600

ヒント

親の型でループを回し、shippingFee() を足していく。

模範解答
public class Main {

    static class Product {
        int shippingFee() { return 600; }
    }

    static class Book extends Product {
        @Override
        int shippingFee() { return 0; }
    }

    static class Furniture extends Product {
        @Override
        int shippingFee() { return 3000; }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product(),
            new Book(),
            new Furniture(),
        };

        int total = 0;
        for (Product item : items) {
            total += item.shippingFee();
        }

        System.out.println(total);
    }
}
解説

if文で種類を判定する必要がない。もし継承を使わなければ、「本なら0、家具なら3000…」という分岐を書き続けることになる。

JV10-S04 【親のメソッドも使う】子クラスから super.メソッド名() で親の処理を呼び、親の結果に追加する形で実装せよ(親が「基本」、子が「基本+拡張」を返す)。 コーディング★★★広告解放
期待される結果

出力 == 基本+拡張

ヒント

super.describe() で親のメソッドを呼べる。

模範解答
public class Main {

    static class Parent {
        String describe() {
            return "基本";
        }
    }

    static class Child extends Parent {
        @Override
        String describe() {
            return super.describe() + "+拡張";
        }
    }

    public static void main(String[] args) {
        System.out.println(new Child().describe());
    }
}
解説

完全に置き換えるのではなく、親の処理に足すことができる。共通処理を親に残しつつ、子で追加の処理を行いたいときに使う。

JV10-S05 【種類の判定と分岐】Product 配列(Product・Book・Furniture)をループし、Book だけ「【書籍】」を付けて出力せよ(他は商品名のみ)。 コーディング★★★広告解放
期待される結果

出力3行

ヒント

instanceof で判定し、Book なら特別な処理をする。

模範解答
public class Main {

    static class Product {
        protected String name;

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

    static class Book extends Product {
        Book(String name) {
            super(name);
        }
    }

    static class Furniture extends Product {
        Furniture(String name) {
            super(name);
        }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("マウス"),
            new Book("Java入門"),
            new Furniture("デスク"),
        };

        for (Product item : items) {
            if (item instanceof Book) {
                System.out.println("【書籍】" + item.name);
            } else {
                System.out.println(item.name);
            }
        }
    }
}
解説

instanceof は必要なときだけ使う。多用しているなら、そもそもメソッドを上書きして解決できないか考えたほうがよい設計になることが多い。

5仕上げ課題

JV10-FINAL 【商品種別ごとの送料計算】継承を使って、種類によって送料が変わる注文システムを作れ。

親クラス Product
protected String nameprotected int price
int shippingFee() … 標準600円
int total() … 価格 + 送料
String category() … 「一般」
toString() … 「[一般] マウス:3,200円+送料600円 = 3,800円」

子クラス Book extends Product
・フィールド追加:private String author
shippingFee()0円(送料無料)
category() → 「書籍」
getAuthor()

子クラス Furniture extends Product
shippingFee()3000円
category() → 「家具」

main の処理
① 3件を Product 配列に入れて全件出力
② 送料の合計を出力
instanceof で書籍だけ著者を出力

データ:マウス/3200、Java入門/2800/山田、デスク/45000

期待される出力(5行)
[一般] マウス:3,200円+送料600円 = 3,800円
[書籍] Java入門:2,800円+送料0円 = 2,800円
[家具] デスク:45,000円+送料3,000円 = 48,000円
送料合計:3,600円
書籍の著者:山田
総合★★★広告解放
期待される結果

出力5行が完全一致

ヒント

toString は親に1回だけ書けばよい。中で category() と shippingFee() を呼べば、子の実装が自動的に使われる。金額は String.format("%,d", 値) で整形。

模範解答
public class Main {

    static class Product {
        protected String name;
        protected int price;

        Product(String name, int price) {
            this.name = name;
            this.price = price;
        }

        int shippingFee() {
            return 600;
        }

        int total() {
            return price + shippingFee();
        }

        String category() {
            return "一般";
        }

        @Override
        public String toString() {
            return "[" + category() + "] " + name + ":"
                    + String.format("%,d", price) + "円+送料"
                    + String.format("%,d", shippingFee()) + "円 = "
                    + String.format("%,d", total()) + "円";
        }
    }

    static class Book extends Product {
        private String author;

        Book(String name, int price, String author) {
            super(name, price);
            this.author = author;
        }

        @Override
        int shippingFee() {
            return 0;
        }

        @Override
        String category() {
            return "書籍";
        }

        String getAuthor() {
            return author;
        }
    }

    static class Furniture extends Product {
        Furniture(String name, int price) {
            super(name, price);
        }

        @Override
        int shippingFee() {
            return 3000;
        }

        @Override
        String category() {
            return "家具";
        }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("マウス", 3200),
            new Book("Java入門", 2800, "山田"),
            new Furniture("デスク", 45000),
        };

        int shippingTotal = 0;

        for (Product item : items) {
            System.out.println(item);
            shippingTotal += item.shippingFee();
        }

        System.out.println("送料合計:" + String.format("%,d", shippingTotal) + "円");

        for (Product item : items) {
            if (item instanceof Book b) {
                System.out.println("書籍の著者:" + b.getAuthor());
            }
        }
    }
}
解説

toString() は親クラスに1回書いただけだ。それなのに3種類とも正しく表示される——category()shippingFee() が、実際の型に応じて子の実装を呼んでいるからだ。親を書いた時点では Book も Furniture も存在していないのに、正しく動く。

これがポリモーフィズムの本質だ。もし継承を使わなければ、こう書くことになる。
if (種類.equals("書籍")) { 送料 = 0; } else if (種類.equals("家具")) { 送料 = 3000; } else { 送料 = 600; }
この分岐が、送料を計算するすべての場所に散らばる。新しい種類を追加するたび、全箇所を探して修正しなければならない——1箇所でも漏れれば不具合になる。

継承なら新しいクラスを1つ足すだけで済む。既存のコードには一切手を触れない。「追加には開いていて、変更には閉じている」——良い設計の指標としてよく語られる原則だ。

ただし継承にも限界がある。Javaでは親クラスを2つ持てない(多重継承の禁止)。「配送可能でもあり、割引可能でもある」といった複数の性質を持たせたいとき、継承だけでは表現できない。

次章のインターフェースが、その制約を解決する。