Java / LESSON 11 / JV11

Java:インターフェース

1学習の目的

2基礎解説

継承は「〜である」(BookはProductである)を表しますが、インターフェースは「〜できる」(配送できる・割引できる)を表します。1つのクラスは親を1つしか持てませんが、インターフェースは何個でも実装できます。

継承(extends)インターフェース(implements)
個数1つだけいくつでも
意味〜である〜できる
フィールド持てる定数のみ
実装あり原則なし(defaultは可)
✅ 覚えるべき重要ポイント
  1. interface は「できることの一覧」。メソッドの名前と形だけを宣言し、中身は実装するクラスが書く。
  2. implements で実装する。複数を同時に実装できる——class SaleItem extends Product implements Shippable, Discountable のように書ける。
  3. インターフェースのメソッドは public が必須。実装側で public を書き忘れると「アクセス範囲を狭められない」というエラーになる。
  4. default を付ければ、インターフェースにも実装を書ける(Java 8以降)。共通の処理を持たせつつ、必要なら上書きもできる。
  5. 抽象クラス(abstract)は「未完成の親クラス」。フィールドを持てるが継承は1つだけ。共通のデータもあるなら抽象クラス、できることだけ揃えるならインターフェース

現場使用例:支払い方法(カード・銀行振込・電子マネー)、通知手段(メール・SMS)、比較可能・並べ替え可能といった性質の表現。

// ① インターフェース=「できること」の宣言
interface Shippable {
    int shippingFee();               // 中身は書かない

    // default なら実装も書ける(Java 8以降)
    default String shippingLabel() {
        return "送料" + shippingFee() + "円";
    }
}

interface Discountable {
    double discountRate();
}

// ② implements で実装する
static class Product implements Shippable {
    protected String name;
    protected int price;

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

    public int shippingFee() {       // ⚠ public が必須
        return 600;
    }
}

// ③ ⭐ 継承と複数のインターフェースを同時に
static class SaleItem extends Product implements Discountable {
    SaleItem(String name, int price) {
        super(name, price);
    }

    public double discountRate() {
        return 0.2;
    }

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

// ④ インターフェース型でまとめて扱える
Shippable[] items = { new Product(…), new SaleItem(…) };
for (Shippable i : items) {
    System.out.println(i.shippingFee());
}

// ⑤ 性質を持つか判定
if (item instanceof Discountable d) {
    System.out.println(d.discountRate());
}

// ⑥ 抽象クラス(フィールドを持てる未完成の親)
static abstract class Shape {
    abstract double area();          // 中身は子が書く

    String describe() {              // 共通処理は書ける
        return "面積は" + area();
    }
}

3基本ドリル(10問)

JV11-D01 インターフェース Shippableint shippingFee())を定義し、Product クラスで実装せよ(600を返す)。結果を出力すること。 コーディング★☆☆無料
期待される結果

出力 == 600

ヒント

interface Shippable { int shippingFee(); } と class Product implements Shippable を書く。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();
    }

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

    public static void main(String[] args) {
        Product p = new Product();

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

インターフェースは「約束」。implements したクラスは、宣言されたメソッドを必ず実装しなければならない。

JV11-D02 1つのクラスが実装できるインターフェースの数はどれか。
A. 1つだけ B. 2つまで C. いくつでも D. 実装できない
選択★☆☆無料
期待される結果

解答 == C

ヒント

継承(extends)との違い。

模範解答
C
解説

継承は1つだけだが、インターフェースは制限なし。「配送できる」「割引できる」「比較できる」を同時に持たせられる。

JV11-D03 2つのインターフェースを同時に実装するクラスを作れ(Shippable:600、Discountable:0.2)。両方の結果を2行出力すること。 コーディング★☆☆無料
期待される結果

出力2行。600 / 0.2

ヒント

implements A, B のようにカンマで並べる。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();
    }

    interface Discountable {
        double discountRate();
    }

    static class Item implements Shippable, Discountable {
        public int shippingFee() {
            return 600;
        }

        public double discountRate() {
            return 0.2;
        }
    }

    public static void main(String[] args) {
        Item i = new Item();

        System.out.println(i.shippingFee());
        System.out.println(i.discountRate());
    }
}
解説

これが継承にはできないこと。JV10で触れた「親を2つ持てない」制約を、インターフェースなら回避できる。

JV11-D04 インターフェースを実装するキーワードを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
public class Main {

    interface Greetable {
        String greet();
    }

    static class Person ____ Greetable {
        public String greet() { return "こんにちは"; }
    }

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

出力 == こんにちは

ヒント

「実装する」を意味する10文字のキーワード。

模範解答
implements
解説

継承は extends、インターフェースは implements。両方使う場合は extends 親 implements インターフェース の順で書く。

JV11-D05 インターフェースを実装するとき、メソッドに必要な修飾子はどれか。
A. private B. public C. protected D. 不要
選択★☆☆無料
期待される結果

解答 == B

ヒント

インターフェースのメソッドは暗黙的に public。

模範解答
B
解説

public を書き忘れるとコンパイルエラー。「継承したメソッドのアクセス範囲を狭めようとしている」と怒られる。

JV11-D06 default メソッドを持つインターフェースを作れ(shippingFee は実装させ、shippingLabel は「送料600円」を返す default とする)。結果を出力すること。 コーディング★★☆広告解放
期待される結果

出力 == 送料600円

ヒント

default String shippingLabel() { return ...; } と書く。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();

        default String shippingLabel() {
            return "送料" + shippingFee() + "円";
        }
    }

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

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

default なら実装クラスが書かなくてよい。共通の処理をインターフェース側に持たせられるので、実装の手間が減る。

JV11-D07 インターフェースに実装を書くキーワードを補ってコードを完成させよ。 穴埋め★★☆広告解放
コード
public class Main {

    interface Greetable {
        String name();

        ____ String greet() {
            return name() + "さん、こんにちは";
        }
    }

    static class Person implements Greetable {
        public String name() { return "山田"; }
    }

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

出力 == 山田さん、こんにちは

ヒント

「既定の」を意味する7文字。Java 8以降で使える。

模範解答
default
解説

default メソッドはインターフェースに実装を持たせる仕組み。既存のインターフェースにメソッドを追加しても、実装クラスを壊さずに済む。

JV11-D08 抽象クラスとインターフェースの違いとして正しいものはどれか。
A. 同じ B. 抽象クラスはフィールドを持てるが継承は1つだけ C. インターフェースのほうが古い D. 抽象クラスは実装できない
選択★★☆広告解放
期待される結果

解答 == B

ヒント

データを持てるかどうかが大きな違い。

模範解答
B
解説

共通のデータもあるなら抽象クラス、できることだけ揃えるならインターフェース——この基準で選ぶ。

JV11-D09 抽象クラス Shape を作れ(abstract double area() と、それを使う describe())。Circle(半径3)で「面積は28.3」と出力すること。 コーディング★★☆広告解放
期待される結果

出力 == 面積は28.3

ヒント

abstract class Shape { abstract double area(); ... } と書く。

模範解答
public class Main {

    static abstract class Shape {
        abstract double area();

        String describe() {
            return "面積は" + String.format("%.1f", area());
        }
    }

    static class Circle extends Shape {
        private double r;

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

        double area() {
            return Math.PI * r * r;
        }
    }

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

abstract なメソッドは中身を書かない。子クラスが必ず実装するので、親は「areaは必ずある」と信じて describe を書ける。

JV11-D10 インターフェース型の配列を作り、2つの実装(600と0)の送料を合計して出力せよ。 コーディング★★☆広告解放
期待される結果

出力 == 600

ヒント

Shippable[] items = { ... }; の形で、異なるクラスをまとめて扱える。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();
    }

    static class Normal implements Shippable {
        public int shippingFee() { return 600; }
    }

    static class Book implements Shippable {
        public int shippingFee() { return 0; }
    }

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

        int total = 0;
        for (Shippable i : items) {
            total += i.shippingFee();
        }

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

継承関係がなくても、同じインターフェースなら一緒に扱える。「共通の親クラス」を無理に作る必要がない。

4実践シナリオ(5問)

JV11-S01 【支払い方法】インターフェース PaymentString pay(int amount))を作り、CreditCard と BankTransfer で実装せよ。1万円の支払いを2行出力すること。 コーディング★★☆広告解放
期待される結果

出力2行

ヒント

それぞれ違うメッセージを返す。

模範解答
public class Main {

    interface Payment {
        String pay(int amount);
    }

    static class CreditCard implements Payment {
        public String pay(int amount) {
            return "カードで" + String.format("%,d", amount) + "円を決済";
        }
    }

    static class BankTransfer implements Payment {
        public String pay(int amount) {
            return "銀行振込で" + String.format("%,d", amount) + "円を請求";
        }
    }

    public static void main(String[] args) {
        Payment[] methods = {new CreditCard(), new BankTransfer()};

        for (Payment m : methods) {
            System.out.println(m.pay(10000));
        }
    }
}
解説

支払い方法が増えてもクラスを足すだけ。呼び出す側のコードは m.pay(金額) のまま変わらない。

JV11-S02 【継承+インターフェース】Product を継承しつつ Discountable も実装する SaleItem を作れ(送料0・割引率0.2)。両方の値を2行出力すること。 コーディング★★☆広告解放
期待される結果

出力2行。0 / 0.2

ヒント

extends Product implements Discountable の順で書く。

模範解答
public class Main {

    interface Discountable {
        double discountRate();
    }

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

    static class SaleItem extends Product implements Discountable {
        @Override
        int shippingFee() { return 0; }

        public double discountRate() { return 0.2; }
    }

    public static void main(String[] args) {
        SaleItem s = new SaleItem();

        System.out.println(s.shippingFee());
        System.out.println(s.discountRate());
    }
}
解説

「商品である」かつ「割引できる」を両立。継承だけでは表現できなかった組み合わせが可能になる。

JV11-S03 【性質の判定】Shippable の配列から、Discountable も持つものだけ割引率を出力せよ(2件中1件が該当)。 コーディング★★☆広告解放
期待される結果

出力 == 0.2

ヒント

instanceof Discountable d で判定する。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();
    }

    interface Discountable {
        double discountRate();
    }

    static class Normal implements Shippable {
        public int shippingFee() { return 600; }
    }

    static class Sale implements Shippable, Discountable {
        public int shippingFee() { return 0; }
        public double discountRate() { return 0.2; }
    }

    public static void main(String[] args) {
        Shippable[] items = {new Normal(), new Sale()};

        for (Shippable i : items) {
            if (i instanceof Discountable d) {
                System.out.println(d.discountRate());
            }
        }
    }
}
解説

「その性質を持つか」で分岐できる。クラスの種類ではなく、能力で判定するほうが柔軟な設計になる。

JV11-S04 【default で共通処理】インターフェースに default メソッド summary() を作り、「マウス:送料600円」の形式を返させよ(name と shippingFee は実装側)。 コーディング★★★広告解放
期待される結果

出力 == マウス:送料600円

ヒント

default メソッドの中から、宣言だけのメソッドを呼べる。

模範解答
public class Main {

    interface Shippable {
        String name();
        int shippingFee();

        default String summary() {
            return name() + ":送料" + shippingFee() + "円";
        }
    }

    static class Product implements Shippable {
        public String name() { return "マウス"; }
        public int shippingFee() { return 600; }
    }

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

default メソッドは他のメソッドを呼べる。共通の組み立て処理をインターフェースに置き、部品だけ実装クラスに任せられる。

JV11-S05 【抽象クラスで共通データ】abstract class Employee(name を持つ)を作り、salary() を抽象メソッドにせよ。Engineer(40万)で「山田:400,000円」と出力すること。 コーディング★★★広告解放
期待される結果

出力 == 山田:400,000円

ヒント

抽象クラスはフィールドとコンストラクタを持てる。

模範解答
public class Main {

    static abstract class Employee {
        protected String name;

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

        abstract int salary();

        @Override
        public String toString() {
            return name + ":" + String.format("%,d", salary()) + "円";
        }
    }

    static class Engineer extends Employee {
        Engineer(String name) {
            super(name);
        }

        int salary() {
            return 400000;
        }
    }

    public static void main(String[] args) {
        System.out.println(new Engineer("山田"));
    }
}
解説

共通のフィールド(name)を持てるのが抽象クラスの利点。インターフェースでは name を持たせられず、各実装クラスで宣言する必要がある。

5仕上げ課題

JV11-FINAL 【配送と割引の設計】インターフェースを使い、柔軟な商品システムを作れ。

インターフェース Shippable
int shippingFee()
String name()
default String shippingLabel() … 送料0なら「送料無料」、それ以外は「送料600円」形式

インターフェース Discountable
double discountRate()

クラス構成
Product implements Shippable … name・price、送料600円
Book extends Product … 送料0円
SaleItem extends Product implements Discountable … 割引率0.2、送料600円

main の処理
① Shippable 配列に3件入れ、「マウス:3,200円(送料600円)」の形式で出力
② 送料の合計を出力
③ Discountable を持つものだけ、割引後価格を「セール品:割引後8,000円」の形式で出力

データ:マウス/3200、Java入門/2800、セール品/10000

期待される出力(5行)
マウス:3,200円(送料600円)
Java入門:2,800円(送料無料)
セール品:10,000円(送料600円)
送料合計:1,200円
セール品:割引後8,000円
総合★★★広告解放
期待される結果

出力5行が完全一致

ヒント

shippingLabel は default メソッドで、送料が0かどうかで表示を切り替える。price を取得するため getPrice() も用意する。割引後は price × (1 - rate) を四捨五入。

模範解答
public class Main {

    interface Shippable {
        int shippingFee();
        String name();

        default String shippingLabel() {
            if (shippingFee() == 0) {
                return "送料無料";
            }
            return "送料" + String.format("%,d", shippingFee()) + "円";
        }
    }

    interface Discountable {
        double discountRate();
    }

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

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

        public String name() {
            return name;
        }

        public int getPrice() {
            return price;
        }

        public int shippingFee() {
            return 600;
        }

        @Override
        public String toString() {
            return name + ":" + String.format("%,d", price)
                    + "円(" + shippingLabel() + ")";
        }
    }

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

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

    static class SaleItem extends Product implements Discountable {
        SaleItem(String name, int price) {
            super(name, price);
        }

        public double discountRate() {
            return 0.2;
        }
    }

    public static void main(String[] args) {
        Shippable[] items = {
            new Product("マウス", 3200),
            new Book("Java入門", 2800),
            new SaleItem("セール品", 10000),
        };

        int shippingTotal = 0;

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

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

        for (Shippable i : items) {
            if (i instanceof Discountable d && i instanceof Product p) {
                long discounted = Math.round(p.getPrice() * (1 - d.discountRate()));
                System.out.println(p.name() + ":割引後"
                        + String.format("%,d", discounted) + "円");
            }
        }
    }
}
解説

この設計では、3つのクラスがそれぞれ違う組み合わせの性質を持っている。Product は配送できるだけ、Book は配送できて送料無料、SaleItem は配送できてさらに割引もできる——継承だけでは、この「割引もできる」を後から足せなかった。

shippingLabel()default にした判断も重要だ。「送料0なら無料と表示する」というルールは全商品に共通なので、各クラスで書くのは無駄になる。かといって Product クラスに書くと、Product を継承しないクラスでは使えない。インターフェースの default なら、実装するすべてのクラスが恩恵を受けられる。

i instanceof Discountable d && i instanceof Product p という書き方にも意味がある。「割引できて、かつ価格を持つ」という2つの条件を同時に確認している。片方だけでは、割引率は分かっても元の価格が取れない。

継承とインターフェースの使い分けはこう考える。「〜である」という本質的な分類は継承(BookはProductである)、「〜できる」という能力はインターフェース(割引できる)。この基準で選べば、たいていの場面で迷わずに済む。

ここまででオブジェクト指向の主要な仕組みは揃った。次章では List と Map を学び、配列では扱いきれなかった可変長のデータを操作できるようになる。