Javaインターフェース完全ガイド

2025-08-01

はじめに

これまでに学んだ継承、ポリモーフィズム、抽象クラスの知識を土台に、Javaの重要な概念である「インターフェース」について詳しく学びましょう。インターフェースはJavaプログラミングにおいて、より柔軟で強力な設計を可能にする仕組みです。

インターフェースの基本概念

インターフェースとは何か?

インターフェースは、クラスが実装すべきメソッドの契約(規約)を定義するものです。具体的な実装を持たず、メソッドのシグネチャ(名前、引数、戻り値の型)のみを定義します。

現実世界の例でインターフェースを例えます。

  • 電源プラグとコンセント:規格が決まっていれば、どのメーカーの家電でも使える
  • USBインターフェース:規格に準拠していれば、どのメーカーの機器でも接続できる

インターフェースの宣言方法

インターフェースは interface キーワードを使って宣言し、クラスと同様にパッケージ内に定義できます。

インターフェース内では、実装を持たない抽象メソッドを宣言できるほか、default メソッドで共通の実装を提供したり、static メソッドでクラスに依存しない処理を定義することも可能です。

// インターフェースの宣言
interface Drawable {
    // 抽象メソッド(実装を持たない)
    void draw();

    // デフォルトメソッド(Java 8以降)
    default void displayInfo() {
        System.out.println("これは描画可能なオブジェクトです");
    }

    // staticメソッド(Java 8以降)
    static void showInterfaceInfo() {
        System.out.println("Drawableインターフェース");
    }
}

このコードは、Drawable というインターフェースを定義した例であり、クラスに「描画できる」という機能的契約を与える仕組みを示しています。
インターフェース内には、実装を持たない抽象メソッド draw() のほか、共通の動作を提供するデフォルトメソッド displayInfo()、およびクラスに依存せず呼び出せる静的メソッド showInterfaceInfo() が定義されています。
つまりこのコードは、Java におけるインターフェースの基本構造と、抽象メソッド・デフォルトメソッド・staticメソッドの使い分けを示す例です。

インターフェースの実装

クラスがインターフェースを利用する際は、implements キーワードを使って実装します。

// インターフェースを実装するクラス
class Circle implements Drawable {
    private String color;
    private double radius;

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

    // インターフェースのメソッドを実装(必須)
    @Override
    public void draw() {
        System.out.println(color + "の円を描きます(半径: " + radius + ")");
    }
}

class Rectangle implements Drawable {
    private String color;
    private double width;
    private double height;

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

    // インターフェースのメソッドを実装
    @Override
    public void draw() {
        System.out.println(color + "の四角形を描きます(幅: " + width + ", 高さ: " + height + ")");
    }
}

このコードは、Drawable インターフェースを実装した Circle クラスと Rectangle クラスの例であり、どちらも draw() メソッドを具体的に実装して図形を描画する処理を定義しています。Circle は円の色と半径を、Rectangle は四角形の色・幅・高さを持ち、それぞれの draw() メソッドで対応する図形の情報を出力します。つまりこのコードは、インターフェースの実装を通じて異なるクラスに共通の動作契約を与え、ポリモーフィズムを実現する例です。

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

構文の比較

次のコードは、抽象クラスとインターフェースの構造と違いを比較して示す例です。

// 抽象クラスの例
abstract class Animal {
    // フィールドを持つことができる
    protected String name;
    protected int age;

    // コンストラクタを持つことができる
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 具象メソッドを持つことができる
    public void eat() {
        System.out.println(name + "が食事をしています");
    }

    // 抽象メソッド
    public abstract void makeSound();
}

// インターフェースの例
interface Swimmable {
    // 定数フィールドのみ可能(public static finalが暗黙的に付与)
    int MAX_DEPTH = 100;

    // 抽象メソッド(public abstractが暗黙的に付与)
    void swim();

    // デフォルトメソッド
    default void floatOnWater() {
        System.out.println("水に浮いています");
    }

    // staticメソッド
    static int getMaxDepth() {
        return MAX_DEPTH;
    }
}

Animal は抽象クラスで、フィールド(name, age)やコンストラクタ、実装を持つ具象メソッド(eat())に加えて、サブクラスで必ず実装すべき抽象メソッド(makeSound())を定義しています。
一方の Swimmable はインターフェースで、すべてのフィールドが暗黙的に public static final、すべてのメソッドが public abstract であり、default メソッドや static メソッドも定義できます。

抽象クラスとインターフェースの主な違い

抽象クラスが「共通の状態と基本動作」を共有する仕組みであるのに対し、インターフェースは「共通の機能契約」を定義する仕組みであることを具体的に示しています。つまり、共通処理を共有したい場合は抽象クラスを、共通の機能仕様を定めたい場合はインターフェースを使うのが基本的な使い分けです。

特徴抽象クラスインターフェース
継承単一継承のみ複数実装可能
フィールド通常のフィールド可能定数のみ(public static final)
コンストラクタ可能不可
メソッド具象メソッドと抽象メソッド抽象メソッド、デフォルトメソッド、staticメソッド
アクセス修飾子任意基本的にpublic
設計の目的「is-a」関係「can-do」能力

使い分けの判断基準

抽象クラスは共通の状態や基本的な処理を持つクラスの土台として使われ、継承関係(is-a)を表現するのに適しています。一方、インターフェースはクラスが実装すべき機能や動作の契約を定義するもので、「何ができるか」(can-do)を表す際に用いられます。

抽象クラスを使う場合:

  • 関連するクラス群で共通の状態や振る舞いを共有する場合
  • テンプレートメソッドパターンのように、基本となるアルゴリズムを提供する場合
  • 「〜の一種である」という関係を表現する場合

インターフェースを使う場合:

  • 異なるクラス階層のオブジェクトに共通の振る舞いを定義する場合
  • 複数の能力(役割)を表現する場合
  • 「〜ができる」という能力を表現する場合

インターフェースの実践的な使い方

複数インターフェースの実装

FlyableRunnableSwimmable はそれぞれ「飛ぶ」「走る」「泳ぐ」機能を定義したインターフェースで、抽象メソッドに加えて共通の動作を持つデフォルトメソッドも定義しています。
Duck クラスはこれら3つのインターフェースをすべて実装し、fly()run()swim() の動作を具体的に定義することで、アヒルが複数の能力を持つことを表現しています。

さらに、Runnable インターフェースのデフォルトメソッド getMaxSpeed() をオーバーライドして独自の走行速度を設定しており、複数のインターフェースを組み合わせて柔軟な機能を実装できることを示しています。

// 複数のインターフェースを定義
interface Flyable {
    void fly();
    default void takeOff() {
        System.out.println("離陸します");
    }
}

interface Runnable {
    void run();
    default int getMaxSpeed() {
        return 20;
    }
}

interface Swimmable {
    void swim();
    default void dive() {
        System.out.println("潜水します");
    }
}

// 複数のインターフェースを実装するクラス
class Duck implements Flyable, Runnable, Swimmable {
    private String name;

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

    @Override
    public void fly() {
        System.out.println(name + "が飛んでいます");
    }

    @Override
    public void run() {
        System.out.println(name + "が走っています");
    }

    @Override
    public void swim() {
        System.out.println(name + "が泳いでいます");
    }

    // デフォルトメソッドをオーバーライド
    @Override
    public int getMaxSpeed() {
        return 10; // アヒルはあまり速く走れない
    }
}

インターフェースの継承

Animal インターフェースは「食べる (eat())」「眠る (sleep())」という基本的な動作を定義し、Pet インターフェースはそれを継承して「遊ぶ (play())」「名前を取得する (getName())」といったペット特有の機能を追加しています。
Dog クラスは Pet インターフェースを実装し、間接的に Animal の契約も引き継いでいるため、すべてのメソッドを具体的に実装しています。

// 基本インターフェース
interface Animal {
    void eat();
    void sleep();
}

// インターフェースの継承
interface Pet extends Animal {
    void play();
    String getName();
}

// 継承されたインターフェースを実装
class Dog implements Pet {
    private String name;

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

    @Override
    public void eat() {
        System.out.println(name + "がドッグフードを食べています");
    }

    @Override
    public void sleep() {
        System.out.println(name + "が寝ています");
    }

    @Override
    public void play() {
        System.out.println(name + "が遊んでいます");
    }

    @Override
    public String getName() {
        return name;
    }
}

つまりこのコードは、インターフェース同士の継承によって機能を拡張し、実装クラスが複数の動作契約をまとめて実現できることを示しています。

ポリモーフィズムとの連携

インターフェースを使ったポリモーフィズム

Shape インターフェースは、図形に共通する3つの操作 ― 面積計算 (calculateArea())、周長計算 (calculatePerimeter())、描画 (draw()) ― を定義しています。
Circle クラスと Square クラスはそれぞれこのインターフェースを実装し、円と正方形に適した具体的な処理を提供しています。

main() メソッドでは、Shape 型の配列に CircleSquare のインスタンスを混在して格納し、共通の shape.draw()shape.calculateArea() を呼び出しています。
これにより、同じメソッド呼び出しでも実際のオブジェクトの型に応じて異なる動作を行うというポリモーフィズムの特性が表れています。

interface Shape {
    double calculateArea();
    double calculatePerimeter();
    void draw();
}

class Circle implements Shape {
    private double radius;

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

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

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

    @Override
    public void draw() {
        System.out.println("円を描画(半径: " + radius + ")");
    }
}

class Square implements Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

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

    @Override
    public double calculatePerimeter() {
        return 4 * side;
    }

    @Override
    public void draw() {
        System.out.println("正方形を描画(一辺: " + side + ")");
    }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        // インターフェース型の配列に様々な実装を格納
        Shape[] shapes = {
            new Circle(5.0),
            new Square(4.0),
            new Circle(3.0),
            new Square(6.0)
        };

        // ポリモーフィズムの力:同じメソッド呼び出しで異なる動作
        for (Shape shape : shapes) {
            shape.draw();
            System.out.printf("面積: %.2f%n", shape.calculateArea());
            System.out.printf("周長: %.2f%n", shape.calculatePerimeter());
            System.out.println("---");
        }
    }
}

つまりこのコードは、インターフェースを使って異なるクラス間で共通の操作を統一的に扱う方法を示した典型的なオブジェクト指向の例です。

メソッドパラメータとしてのインターフェース

Logger インターフェースは、情報出力用の log() メソッドとエラー出力用の error() メソッドを定義し、ConsoleLoggerFileLogger がそれぞれコンソール出力とファイル出力の具体的な実装を提供しています。Application クラスは、コンストラクタで Logger 型を受け取り、どのログ方法を使うかを外部から指定できる構造になっています。

main() メソッドでは、Application に異なる Logger 実装(ConsoleLoggerFileLogger)を渡して動作を切り替えており、**インターフェースを介した実装の独立性と柔軟性(疎結合設計)**を実践的に示しています。

interface Logger {
    void log(String message);
    void error(String message);
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("[INFO] " + message);
    }

    @Override
    public void error(String message) {
        System.err.println("[ERROR] " + message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // ファイルに書き込む処理(簡略化)
        System.out.println("ファイルに書き込み: [INFO] " + message);
    }

    @Override
    public void error(String message) {
        // ファイルに書き込む処理(簡略化)
        System.out.println("ファイルに書き込み: [ERROR] " + message);
    }
}

class Application {
    private Logger logger;

    // コンストラクタでインターフェースを受け取る
    public Application(Logger logger) {
        this.logger = logger;
    }

    public void process() {
        logger.log("処理を開始します");
        // 何らかの処理
        logger.log("処理が完了しました");
    }

    public void handleError() {
        logger.error("エラーが発生しました");
    }
}

public class InterfaceAsParameter {
    public static void main(String[] args) {
        // 異なるLogger実装を渡せる
        Application app1 = new Application(new ConsoleLogger());
        Application app2 = new Application(new FileLogger());

        app1.process();
        app2.process();
    }
}

実践的な設計例

支払いシステムの設計

PaymentMethod インターフェースは、支払い処理を行う processPayment()、支払い情報を返す getPaymentDetails()、および金額の妥当性を確認する validateAmount()(デフォルトメソッド)を定義しています。

CreditCardPayment クラスはクレジットカードでの支払いを実装し、カード番号の一部をマスク表示します。ElectronicMoneyPayment クラスは電子マネー決済を実装し、残高を確認して不足している場合は支払いを拒否します。

PaymentProcessor クラスは、渡された PaymentMethod の種類に依存せず同じ手順で支払い処理を実行でき、成功・失敗を判定します。main() メソッドでは、異なる決済方法(クレジットカード・電子マネー)を同一のインターフェース経由で処理しており、ポリモーフィズムによる柔軟な拡張性と再利用性の高い設計を実現しています。

// 支払い方法のインターフェース
interface PaymentMethod {
    boolean processPayment(double amount);
    String getPaymentDetails();
    default boolean validateAmount(double amount) {
        return amount > 0;
    }
}

// クレジットカード支払い
class CreditCardPayment implements PaymentMethod {
    private String cardNumber;
    private String cardHolder;

    public CreditCardPayment(String cardNumber, String cardHolder) {
        this.cardNumber = cardNumber;
        this.cardHolder = cardHolder;
    }

    @Override
    public boolean processPayment(double amount) {
        if (!validateAmount(amount)) {
            System.out.println("無効な金額です");
            return false;
        }

        // 実際の処理は簡略化
        System.out.printf("クレジットカードで%.2f円支払いを処理します%n", amount);
        System.out.println("カード番号: " + maskCardNumber(cardNumber));
        return true;
    }

    @Override
    public String getPaymentDetails() {
        return "クレジットカード: " + cardHolder + " (" + maskCardNumber(cardNumber) + ")";
    }

    private String maskCardNumber(String cardNumber) {
        return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
    }
}

// 電子マネー支払い
class ElectronicMoneyPayment implements PaymentMethod {
    private String emoneyId;
    private double balance;

    public ElectronicMoneyPayment(String emoneyId, double balance) {
        this.emoneyId = balance;
        this.balance = balance;
    }

    @Override
    public boolean processPayment(double amount) {
        if (!validateAmount(amount)) {
            System.out.println("無効な金額です");
            return false;
        }

        if (amount > balance) {
            System.out.println("残高が不足しています");
            return false;
        }

        balance -= amount;
        System.out.printf("電子マネーで%.2f円支払いしました%n", amount);
        System.out.println("残高: " + balance + "円");
        return true;
    }

    @Override
    public String getPaymentDetails() {
        return "電子マネー: " + emoneyId + " (残高: " + balance + "円)";
    }
}

// 支払い処理クラス
class PaymentProcessor {
    public void processOrder(PaymentMethod payment, double amount) {
        System.out.println("=== 支払い処理開始 ===");
        System.out.println(payment.getPaymentDetails());

        if (payment.processPayment(amount)) {
            System.out.println("支払いが成功しました");
        } else {
            System.out.println("支払いに失敗しました");
        }
        System.out.println("===================");
    }
}

public class PaymentSystem {
    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor();

        PaymentMethod[] payments = {
            new CreditCardPayment("1234567812345678", "山田太郎"),
            new ElectronicMoneyPayment("EM00123456", 5000.0)
        };

        double amount = 2500.0;
        for (PaymentMethod payment : payments) {
            processor.processOrder(payment, amount);
            System.out.println();
        }
    }
}

ソート可能なオブジェクトの設計

Sortable インターフェースは、オブジェクトがソート可能であることを示す契約として getSortValue() メソッドを定義しています。Student クラスは点数 (score) を、Product クラスは価格 (price) をソート基準として getSortValue() を実装し、それぞれ異なる観点で並び替え可能になります。

SortUtils クラスはユーティリティとして、Arrays.sort()Comparator を用いて昇順・降順でソートするメソッドを提供し、displayItems() で結果を出力します。

main() メソッドでは、Student 配列と Product 配列の両方を同じ SortUtils クラスのメソッドで並び替えており、インターフェースを使うことで型に依存しない汎用的な処理を実現できることを示しています。

import java.util.Arrays;
import java.util.Comparator;

// ソート可能なことを示すインターフェース
interface Sortable {
    int getSortValue();
}

// 学生クラス
class Student implements Sortable {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int getSortValue() {
        return score; // 点数でソート
    }

    @Override
    public String toString() {
        return name + ": " + score + "点";
    }
}

// 商品クラス
class Product implements Sortable {
    private String name;
    private int price;

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

    @Override
    public int getSortValue() {
        return price; // 価格でソート
    }

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

// ソートユーティリティクラス
class SortUtils {
    public static void sortByValue(Sortable[] items) {
        Arrays.sort(items, Comparator.comparingInt(Sortable::getSortValue));
    }

    public static void sortByValueDescending(Sortable[] items) {
        Arrays.sort(items, Comparator.comparingInt(Sortable::getSortValue).reversed());
    }

    public static void displayItems(Sortable[] items) {
        for (Sortable item : items) {
            System.out.println(item);
        }
    }
}

public class SortableExample {
    public static void main(String[] args) {
        // 学生のソート
        Student[] students = {
            new Student("山田", 85),
            new Student("佐藤", 92),
            new Student("鈴木", 76)
        };

        System.out.println("=== 学生を点数順にソート ===");
        SortUtils.sortByValue(students);
        SortUtils.displayItems(students);

        System.out.println();

        // 商品のソート
        Product[] products = {
            new Product("ノートパソコン", 120000),
            new Product("マウス", 2500),
            new Product("キーボード", 5000)
        };

        System.out.println("=== 商品を価格順にソート(降順)===");
        SortUtils.sortByValueDescending(products);
        SortUtils.displayItems(products);
    }
}

抽象クラスとインターフェース両方を活用した設計

抽象クラス Vehicle は、すべての乗り物に共通するフィールド(name, speed)や動作(start(), stop())を持ち、具体的な移動方法を定義する move() を抽象メソッドとして宣言しています。
一方、FlyableSwimmableDrivable はそれぞれ「飛ぶ」「泳ぐ」「走る」といった特定の能力を定義するインターフェースです。

Airplane クラスは Vehicle を継承し、Flyable を実装することで「飛行機」としての機能(飛行や高度情報)を実現しています。
AmphibiousVehicle クラスは Vehicle を継承し、DrivableSwimmable の両方を実装して「陸と水の両方を移動できる車」を表現しています。

main() メソッドでは、Vehicle 型の配列を使って異なる乗り物を統一的に扱い、instanceof を用いて実際の能力(飛行・走行・潜水)を判別してメソッドを呼び出しています。

// 抽象クラス:共通の基本機能を提供
abstract class Vehicle {
    protected String name;
    protected int speed;

    public Vehicle(String name, int speed) {
        this.name = name;
        this.speed = speed;
    }

    // 具象メソッド
    public void start() {
        System.out.println(name + "が発進します");
    }

    public void stop() {
        System.out.println(name + "が停止します");
    }

    // 抽象メソッド
    public abstract void move();
}

// インターフェース:特定の能力を定義
interface Flyable {
    void fly();
    int getMaxAltitude();
}

interface Swimmable {
    void swim();
    int getMaxDepth();
}

interface Drivable {
    void drive();
    int getMaxSpeed();
}

// 具象クラス:抽象クラスを継承し、インターフェースを実装
class Airplane extends Vehicle implements Flyable {
    private int maxAltitude;

    public Airplane(String name, int speed, int maxAltitude) {
        super(name, speed);
        this.maxAltitude = maxAltitude;
    }

    @Override
    public void move() {
        System.out.println(name + "が空を飛んで移動します");
    }

    @Override
    public void fly() {
        System.out.println(name + "が高度" + maxAltitude + "mで飛行中");
    }

    @Override
    public int getMaxAltitude() {
        return maxAltitude;
    }
}

class AmphibiousVehicle extends Vehicle implements Drivable, Swimmable {
    private int maxDepth;

    public AmphibiousVehicle(String name, int speed, int maxDepth) {
        super(name, speed);
        this.maxDepth = maxDepth;
    }

    @Override
    public void move() {
        System.out.println(name + "が陸と水の両方を移動します");
    }

    @Override
    public void drive() {
        System.out.println(name + "が道路上を走行中");
    }

    @Override
    public void swim() {
        System.out.println(name + "が水深" + maxDepth + "mまで潜水可能");
    }

    @Override
    public int getMaxDepth() {
        return maxDepth;
    }

    @Override
    public int getMaxSpeed() {
        return speed;
    }
}

public class CombinedExample {
    public static void main(String[] args) {
        Vehicle[] vehicles = {
            new Airplane("ジェット機", 900, 10000),
            new AmphibiousVehicle("水陸両用車", 80, 10)
        };

        for (Vehicle vehicle : vehicles) {
            vehicle.start();
            vehicle.move();

            // インターフェースのメソッドを呼び出す
            if (vehicle instanceof Flyable) {
                ((Flyable) vehicle).fly();
            }

            if (vehicle instanceof Swimmable) {
                ((Swimmable) vehicle).swim();
            }

            if (vehicle instanceof Drivable) {
                ((Drivable) vehicle).drive();
            }

            vehicle.stop();
            System.out.println("---");
        }
    }
}

つまりこのコードは、抽象クラスで共通機能を、インターフェースで拡張的な能力を定義することで、再利用性・柔軟性・多態性を兼ね備えたオブジェクト指向設計を示しています。

まとめ

インターフェースは、実装クラスが守るべき契約を定義し、複数のインターフェースを同時に実装できる多重実装を可能にすることで、ポリモーフィズムを通じて多様な実装を統一的に扱える仕組みを提供します。これにより、実装詳細に依存しない疎結合な設計や、モックオブジェクトを用いたテストの容易化が実現します。

抽象クラスが「何であるか(is-a関係)」を表し共通の状態や振る舞いを共有するのに対し、インターフェースは「何ができるか(can-do関係)」を定義して振る舞いの契約を明確にします。実際の開発では、インターフェースを活用することでコードの保守性や拡張性を高め、テストを容易にし、チーム開発における役割分担を明確にできます。

インターフェースを適切に使い分けることで、より柔軟で拡張性の高いプログラムを設計できるようになります。まずは簡単な例から始めて、徐々に複雑なシステム設計に応用していきましょう。

演習問題

初級問題(3問)

初級問題1: 基本的なインターフェースの実装

以下のDrawableインターフェースを実装する2つのクラスを作成してください。

interface Drawable {
    void draw();
    String getColor();
}

// CircleクラスとRectangleクラスを実装してください
// 各クラスはcolorフィールドを持ち、コンストラクタで初期化する
// draw()メソッドでは図形の情報を表示する

public class Main {
    public static void main(String[] args) {
        Drawable[] shapes = {
            new Circle("赤", 5.0),
            new Rectangle("青", 4.0, 6.0)
        };

        for (Drawable shape : shapes) {
            shape.draw();
            System.out.println("色: " + shape.getColor());
        }
    }
}

実行結果:

赤色の円を描画します(半径: 5.0)
色: 赤
青色の四角形を描画します(幅: 4.0, 高さ: 6.0)
色: 青

初級問題2: 複数インターフェースの実装

以下の2つのインターフェースを実装するSmartPhoneクラスを作成してください。

interface Callable {
    void makeCall(String number);
    void receiveCall(String number);
}

interface Messageable {
    void sendMessage(String number, String message);
    void receiveMessage(String number, String message);
}

// SmartPhoneクラスを実装してください
// フィールド: owner(所有者名)
// コンストラクタでownerを初期化
// 各メソッドでは適切なメッセージを表示

public class Main {
    public static void main(String[] args) {
        SmartPhone phone = new SmartPhone("山田太郎");
        phone.makeCall("03-1234-5678");
        phone.receiveCall("090-1111-2222");
        phone.sendMessage("090-3333-4444", "こんにちは");
        phone.receiveMessage("090-5555-6666", "明日会いましょう");
    }
}

実行結果:

山田太郎が03-1234-5678に電話をかけます
山田太郎が090-1111-2222からの電話を受けました
山田太郎が090-3333-4444にメッセージを送信: こんにちは
山田太郎が090-5555-6666からメッセージを受信: 明日会いましょう

初級問題3: インターフェースのデフォルトメソッド

以下のインターフェースを完成させ、Animalインターフェースを実装するクラスを作成してください。

interface Animal {
    // 抽象メソッド
    void makeSound();

    // デフォルトメソッド
    default void sleep() {
        System.out.println("動物が眠っています");
    }

    // staticメソッド
    static void showCategory() {
        System.out.println("これは動物のインターフェースです");
    }
}

// DogクラスとCatクラスを実装してください
// makeSound()メソッドでそれぞれの鳴き声を表示
// Dogクラスではsleep()メソッドをオーバーライド

public class Main {
    public static void main(String[] args) {
        Animal.showCategory();

        Animal[] animals = {
            new Dog("ポチ"),
            new Cat("タマ")
        };

        for (Animal animal : animals) {
            animal.makeSound();
            animal.sleep();
        }
    }
}

実行結果:

これは動物のインターフェースです
ポチがワンワンと吠えます
ポチが小屋で眠っています
タマがニャーと鳴きます
動物が眠っています

中級問題(6問)

中級問題1: インターフェースとポリモーフィズム

図形の面積と周長を計算するシステムを作成してください。

interface Calculatable {
    double calculateArea();
    double calculatePerimeter();
}

// Circle, Square, Triangleクラスを実装
// 各図形クラスは適切なフィールドとコンストラクタを持つ
// ポリモーフィズムを活用して処理する

public class Main {
    public static void main(String[] args) {
        Calculatable[] shapes = {
            new Circle(5.0),
            new Square(4.0),
            new Triangle(3.0, 4.0, 5.0)
        };

        for (Calculatable shape : shapes) {
            System.out.printf("面積: %.2f%n", shape.calculateArea());
            System.out.printf("周長: %.2f%n", shape.calculatePerimeter());
            System.out.println("---");
        }
    }
}

中級問題2: インターフェースの継承

インターフェースの継承関係を作成してください。

interface Vehicle {
    void start();
    void stop();
}

// LandVehicleインターフェース(Vehicleを継承、driveメソッド追加)
// AirVehicleインターフェース(Vehicleを継承、flyメソッド追加)

// Carクラス(LandVehicle実装)
// Airplaneクラス(AirVehicle実装)

public class Main {
    public static void main(String[] args) {
        Vehicle[] vehicles = {
            new Car("トヨタ"),
            new Airplane("ボーイング")
        };

        for (Vehicle vehicle : vehicles) {
            vehicle.start();

            if (vehicle instanceof LandVehicle) {
                ((LandVehicle) vehicle).drive();
            }
            if (vehicle instanceof AirVehicle) {
                ((AirVehicle) vehicle).fly();
            }

            vehicle.stop();
        }
    }
}

中級問題3: 抽象クラスとインターフェースの組み合わせ

銀行口座システムを設計してください。

interface InterestBearing {
    double calculateInterest();
    void applyInterest();
}

abstract class BankAccount {
    protected String accountNumber;
    protected double balance;

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

    public abstract void withdraw(double amount);
    public void deposit(double amount) {
        balance += amount;
    }

    public double getBalance() {
        return balance;
    }
}

// SavingsAccountクラス(BankAccount継承、InterestBearing実装)
// CheckingAccountクラス(BankAccount継承)

public class Main {
    public static void main(String[] args) {
        BankAccount[] accounts = {
            new SavingsAccount("SAV001", 10000, 0.02),
            new CheckingAccount("CHK001", 5000)
        };

        for (BankAccount account : accounts) {
            if (account instanceof InterestBearing) {
                ((InterestBearing) account).applyInterest();
            }
            System.out.println("残高: " + account.getBalance());
        }
    }
}

中級問題4: ソート可能なインターフェース

Comparableインターフェースのような機能を実装してください。

interface Sortable {
    int getSortKey();
}

// Studentクラス(名前、点数、点数でソート)
// Productクラス(商品名、価格、価格でソート)

class SortUtils {
    public static void sort(Sortable[] items) {
        // バブルソートで実装
        for (int i = 0; i < items.length - 1; i++) {
            for (int j = 0; j < items.length - 1 - i; j++) {
                if (items[j].getSortKey() > items[j + 1].getSortKey()) {
                    Sortable temp = items[j];
                    items[j] = items[j + 1];
                    items[j + 1] = temp;
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Sortable[] items = {
            new Student("山田", 85),
            new Student("佐藤", 92),
            new Student("鈴木", 76),
            new Product("ノートPC", 120000),
            new Product("マウス", 2500),
            new Product("キーボード", 5000)
        };

        SortUtils.sort(items);

        for (Sortable item : items) {
            System.out.println(item);
        }
    }
}

中級問題5: イベントリスナーインターフェース

ボタンクリックのイベント処理システムを作成してください。

interface ClickListener {
    void onClick();
    void onDoubleClick();
}

class Button {
    private String label;
    private ClickListener listener;

    public Button(String label) {
        this.label = label;
    }

    public void setClickListener(ClickListener listener) {
        this.listener = listener;
    }

    public void click() {
        System.out.println(label + "がクリックされました");
        if (listener != null) {
            listener.onClick();
        }
    }

    public void doubleClick() {
        System.out.println(label + "がダブルクリックされました");
        if (listener != null) {
            listener.onDoubleClick();
        }
    }
}

// OKButtonListenerクラスとCancelButtonListenerクラスを実装

public class Main {
    public static void main(String[] args) {
        Button okButton = new Button("OK");
        Button cancelButton = new Button("キャンセル");

        okButton.setClickListener(new OKButtonListener());
        cancelButton.setClickListener(new CancelButtonListener());

        okButton.click();
        okButton.doubleClick();
        cancelButton.click();
    }
}

中級問題6: データベース接続インターフェース

データベース接続のインターフェースを設計してください。

interface DatabaseConnection {
    void connect();
    void disconnect();
    void executeQuery(String query);
    boolean isConnected();
}

// MySQLConnectionクラスとOracleConnectionクラスを実装

class DatabaseManager {
    public static void testConnection(DatabaseConnection connection) {
        connection.connect();
        if (connection.isConnected()) {
            connection.executeQuery("SELECT * FROM users");
        }
        connection.disconnect();
    }
}

public class Main {
    public static void main(String[] args) {
        DatabaseConnection[] connections = {
            new MySQLConnection("localhost", "mydb"),
            new OracleConnection("oracle-server", "oracledb")
        };

        for (DatabaseConnection connection : connections) {
            DatabaseManager.testConnection(connection);
        }
    }
}

上級問題(3問)

上級問題1: プラグインシステムの設計

プラグイン機構を持つテキストエディタを設計してください。

interface Plugin {
    String getName();
    String getVersion();
    void initialize();
    void execute(String text);
    void cleanup();
}

abstract class TextEditor {
    protected String currentText;
    protected List plugins;

    public TextEditor() {
        this.plugins = new ArrayList<>();
        this.currentText = "";
    }

    public void addPlugin(Plugin plugin) {
        plugins.add(plugin);
        plugin.initialize();
    }

    public void setText(String text) {
        this.currentText = text;
        applyPlugins();
    }

    protected void applyPlugins() {
        for (Plugin plugin : plugins) {
            plugin.execute(currentText);
        }
    }

    public abstract void display();
}

// SpellCheckPlugin, FormatPlugin, StatisticsPluginを実装
// AdvancedTextEditorクラス(TextEditor継承)

public class Main {
    public static void main(String[] args) {
        TextEditor editor = new AdvancedTextEditor();

        editor.addPlugin(new SpellCheckPlugin());
        editor.addPlugin(new FormatPlugin());
        editor.addPlugin(new StatisticsPlugin());

        editor.setText("Hello World! This is a sample text.");
        editor.display();
    }
}

上級問題2: ストラテジーパターンの実装

支払い方法のストラテジーパターンを実装してください。

interface PaymentStrategy {
    boolean pay(double amount);
    String getPaymentDetails();
    default boolean validateAmount(double amount) {
        return amount > 0;
    }
}

class ShoppingCart {
    private List items;
    private PaymentStrategy paymentStrategy;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(String item) {
        items.add(item);
    }

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public boolean checkout(double amount) {
        if (paymentStrategy == null) {
            System.out.println("支払い方法が設定されていません");
            return false;
        }

        System.out.println("カート内の商品: " + items);
        System.out.println("合計金額: " + amount);

        return paymentStrategy.pay(amount);
    }
}

// CreditCardPayment, PayPalPayment, BitcoinPaymentを実装

public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.addItem("ノートPC");
        cart.addItem("マウス");

        // 支払い方法を切り替えてテスト
        PaymentStrategy[] strategies = {
            new CreditCardPayment("1234-5678-9012-3456", "山田太郎"),
            new PayPalPayment("yamada@example.com"),
            new BitcoinPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
        };

        for (PaymentStrategy strategy : strategies) {
            cart.setPaymentStrategy(strategy);
            boolean success = cart.checkout(150000);
            System.out.println("支払い結果: " + (success ? "成功" : "失敗"));
            System.out.println("---");
        }
    }
}

上級問題3: オブザーバーパターンの実装

天気観測システムをオブザーバーパターンで実装してください。

interface Observer {
    void update(float temperature, float humidity, float pressure);
}

interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

class WeatherData implements Subject {
    private List observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        this.observers = new ArrayList<>();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    private void measurementsChanged() {
        notifyObservers();
    }

    // Subjectインターフェースのメソッドを実装
}

// CurrentConditionsDisplay, StatisticsDisplay, ForecastDisplayを実装

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        Observer currentDisplay = new CurrentConditionsDisplay();
        Observer statisticsDisplay = new StatisticsDisplay();
        Observer forecastDisplay = new ForecastDisplay();

        weatherData.registerObserver(currentDisplay);
        weatherData.registerObserver(statisticsDisplay);
        weatherData.registerObserver(forecastDisplay);

        // 天気データの変更をシミュレート
        weatherData.setMeasurements(25.0f, 65.0f, 1013.0f);
        System.out.println("---");
        weatherData.setMeasurements(22.0f, 70.0f, 1012.0f);
        System.out.println("---");

        // オブザーバーを削除
        weatherData.removeObserver(forecastDisplay);
        weatherData.setMeasurements(20.0f, 75.0f, 1011.0f);
    }
}

解答例

初級問題1 解答例

interface Drawable {
    void draw();
    String getColor();
}

class Circle implements Drawable {
    private String color;
    private double radius;

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

    @Override
    public void draw() {
        System.out.println(color + "色の円を描画します(半径: " + radius + ")");
    }

    @Override
    public String getColor() {
        return color;
    }
}

class Rectangle implements Drawable {
    private String color;
    private double width;
    private double height;

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

    @Override
    public void draw() {
        System.out.println(color + "色の四角形を描画します(幅: " + width + ", 高さ: " + height + ")");
    }

    @Override
    public String getColor() {
        return color;
    }
}

public class Main {
    public static void main(String[] args) {
        Drawable[] shapes = {
            new Circle("赤", 5.0),
            new Rectangle("青", 4.0, 6.0)
        };

        for (Drawable shape : shapes) {
            shape.draw();
            System.out.println("色: " + shape.getColor());
        }
    }
}

初級問題2 解答例

interface Callable {
    void makeCall(String number);
    void receiveCall(String number);
}

interface Messageable {
    void sendMessage(String number, String message);
    void receiveMessage(String number, String message);
}

class SmartPhone implements Callable, Messageable {
    private String owner;

    public SmartPhone(String owner) {
        this.owner = owner;
    }

    @Override
    public void makeCall(String number) {
        System.out.println(owner + "が" + number + "に電話をかけます");
    }

    @Override
    public void receiveCall(String number) {
        System.out.println(owner + "が" + number + "からの電話を受けました");
    }

    @Override
    public void sendMessage(String number, String message) {
        System.out.println(owner + "が" + number + "にメッセージを送信: " + message);
    }

    @Override
    public void receiveMessage(String number, String message) {
        System.out.println(owner + "が" + number + "からメッセージを受信: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        SmartPhone phone = new SmartPhone("山田太郎");
        phone.makeCall("03-1234-5678");
        phone.receiveCall("090-1111-2222");
        phone.sendMessage("090-3333-4444", "こんにちは");
        phone.receiveMessage("090-5555-6666", "明日会いましょう");
    }
}

初級問題3 解答例

interface Animal {
    void makeSound();

    default void sleep() {
        System.out.println("動物が眠っています");
    }

    static void showCategory() {
        System.out.println("これは動物のインターフェースです");
    }
}

class Dog implements Animal {
    private String name;

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

    @Override
    public void makeSound() {
        System.out.println(name + "がワンワンと吠えます");
    }

    @Override
    public void sleep() {
        System.out.println(name + "が小屋で眠っています");
    }
}

class Cat implements Animal {
    private String name;

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

    @Override
    public void makeSound() {
        System.out.println(name + "がニャーと鳴きます");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal.showCategory();

        Animal[] animals = {
            new Dog("ポチ"),
            new Cat("タマ")
        };

        for (Animal animal : animals) {
            animal.makeSound();
            animal.sleep();
        }
    }
}

中級問題1 解答例

interface Calculatable {
    double calculateArea();
    double calculatePerimeter();
}

class Circle implements Calculatable {
    private double radius;

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

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

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

class Square implements Calculatable {
    private double side;

    public Square(double side) {
        this.side = side;
    }

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

    @Override
    public double calculatePerimeter() {
        return 4 * side;
    }
}

class Triangle implements Calculatable {
    private double side1, side2, side3;

    public Triangle(double side1, double side2, double side3) {
        this.side1 = side1;
        this.side2 = side2;
        this.side3 = side3;
    }

    @Override
    public double calculateArea() {
        // ヘロンの公式
        double s = (side1 + side2 + side3) / 2;
        return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
    }

    @Override
    public double calculatePerimeter() {
        return side1 + side2 + side3;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculatable[] shapes = {
            new Circle(5.0),
            new Square(4.0),
            new Triangle(3.0, 4.0, 5.0)
        };

        for (Calculatable shape : shapes) {
            System.out.printf("面積: %.2f%n", shape.calculateArea());
            System.out.printf("周長: %.2f%n", shape.calculatePerimeter());
            System.out.println("---");
        }
    }
}

中級問題2 解答例

interface Vehicle {
    void start();
    void stop();
}

interface LandVehicle extends Vehicle {
    void drive();
}

interface AirVehicle extends Vehicle {
    void fly();
}

class Car implements LandVehicle {
    private String brand;

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

    @Override
    public void start() {
        System.out.println(brand + "の車がエンジンを始動しました");
    }

    @Override
    public void stop() {
        System.out.println(brand + "の車が停止しました");
    }

    @Override
    public void drive() {
        System.out.println(brand + "の車が走行中です");
    }
}

class Airplane implements AirVehicle {
    private String model;

    public Airplane(String model) {
        this.model = model;
    }

    @Override
    public void start() {
        System.out.println(model + "が離陸準備を開始しました");
    }

    @Override
    public void stop() {
        System.out.println(model + "が着陸しました");
    }

    @Override
    public void fly() {
        System.out.println(model + "が飛行中です");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle[] vehicles = {
            new Car("トヨタ"),
            new Airplane("ボーイング")
        };

        for (Vehicle vehicle : vehicles) {
            vehicle.start();

            if (vehicle instanceof LandVehicle) {
                ((LandVehicle) vehicle).drive();
            }
            if (vehicle instanceof AirVehicle) {
                ((AirVehicle) vehicle).fly();
            }

            vehicle.stop();
        }
    }
}

中級問題3 解答例

interface InterestBearing {
    double calculateInterest();
    void applyInterest();
}

abstract class BankAccount {
    protected String accountNumber;
    protected double balance;

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

    public abstract void withdraw(double amount);

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println(amount + "円預け入れました");
        }
    }

    public double getBalance() {
        return balance;
    }
}

class SavingsAccount extends BankAccount implements InterestBearing {
    private double interestRate;

    public SavingsAccount(String accountNumber, double initialBalance, double interestRate) {
        super(accountNumber, initialBalance);
        this.interestRate = interestRate;
    }

    @Override
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println(amount + "円引き出しました");
        } else {
            System.out.println("残高不足または無効な金額です");
        }
    }

    @Override
    public double calculateInterest() {
        return balance * interestRate;
    }

    @Override
    public void applyInterest() {
        double interest = calculateInterest();
        balance += interest;
        System.out.println("利息 " + interest + "円が付与されました");
    }
}

class CheckingAccount extends BankAccount {
    public CheckingAccount(String accountNumber, double initialBalance) {
        super(accountNumber, initialBalance);
    }

    @Override
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println(amount + "円引き出しました");
        } else {
            System.out.println("残高不足または無効な金額です");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount[] accounts = {
            new SavingsAccount("SAV001", 10000, 0.02),
            new CheckingAccount("CHK001", 5000)
        };

        for (BankAccount account : accounts) {
            if (account instanceof InterestBearing) {
                ((InterestBearing) account).applyInterest();
            }
            System.out.println("残高: " + account.getBalance());
            System.out.println("---");
        }
    }
}

中級問題4 解答例

interface Sortable {
    int getSortKey();
}

class Student implements Sortable {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int getSortKey() {
        return score;
    }

    @Override
    public String toString() {
        return name + ": " + score + "点";
    }
}

class Product implements Sortable {
    private String name;
    private int price;

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

    @Override
    public int getSortKey() {
        return price;
    }

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

class SortUtils {
    public static void sort(Sortable[] items) {
        for (int i = 0; i < items.length - 1; i++) {
            for (int j = 0; j < items.length - 1 - i; j++) {
                if (items[j].getSortKey() > items[j + 1].getSortKey()) {
                    Sortable temp = items[j];
                    items[j] = items[j + 1];
                    items[j + 1] = temp;
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Sortable[] items = {
            new Student("山田", 85),
            new Student("佐藤", 92),
            new Student("鈴木", 76),
            new Product("ノートPC", 120000),
            new Product("マウス", 2500),
            new Product("キーボード", 5000)
        };

        SortUtils.sort(items);

        for (Sortable item : items) {
            System.out.println(item);
        }
    }
}

中級問題5 解答例

interface ClickListener {
    void onClick();
    void onDoubleClick();
}

class Button {
    private String label;
    private ClickListener listener;

    public Button(String label) {
        this.label = label;
    }

    public void setClickListener(ClickListener listener) {
        this.listener = listener;
    }

    public void click() {
        System.out.println(label + "がクリックされました");
        if (listener != null) {
            listener.onClick();
        }
    }

    public void doubleClick() {
        System.out.println(label + "がダブルクリックされました");
        if (listener != null) {
            listener.onDoubleClick();
        }
    }
}

class OKButtonListener implements ClickListener {
    @Override
    public void onClick() {
        System.out.println("OKがクリックされました - 処理を実行します");
    }

    @Override
    public void onDoubleClick() {
        System.out.println("OKがダブルクリックされました - 詳細設定を表示します");
    }
}

class CancelButtonListener implements ClickListener {
    @Override
    public void onClick() {
        System.out.println("キャンセルがクリックされました - 操作を中止します");
    }

    @Override
    public void onDoubleClick() {
        System.out.println("キャンセルがダブルクリックされました - すべての変更を破棄します");
    }
}

public class Main {
    public static void main(String[] args) {
        Button okButton = new Button("OK");
        Button cancelButton = new Button("キャンセル");

        okButton.setClickListener(new OKButtonListener());
        cancelButton.setClickListener(new CancelButtonListener());

        okButton.click();
        okButton.doubleClick();
        cancelButton.click();
        cancelButton.doubleClick();
    }
}

中級問題6 解答例

interface DatabaseConnection {
    void connect();
    void disconnect();
    void executeQuery(String query);
    boolean isConnected();
}

class MySQLConnection implements DatabaseConnection {
    private String host;
    private String database;
    private boolean connected;

    public MySQLConnection(String host, String database) {
        this.host = host;
        this.database = database;
        this.connected = false;
    }

    @Override
    public void connect() {
        System.out.println("MySQLに接続中: " + host + "/" + database);
        connected = true;
        System.out.println("MySQL接続完了");
    }

    @Override
    public void disconnect() {
        if (connected) {
            System.out.println("MySQL接続を切断します");
            connected = false;
        }
    }

    @Override
    public void executeQuery(String query) {
        if (connected) {
            System.out.println("MySQLクエリ実行: " + query);
        } else {
            System.out.println("エラー: データベースに接続されていません");
        }
    }

    @Override
    public boolean isConnected() {
        return connected;
    }
}

class OracleConnection implements DatabaseConnection {
    private String host;
    private String database;
    private boolean connected;

    public OracleConnection(String host, String database) {
        this.host = host;
        this.database = database;
        this.connected = false;
    }

    @Override
    public void connect() {
        System.out.println("Oracleに接続中: " + host + "/" + database);
        connected = true;
        System.out.println("Oracle接続完了");
    }

    @Override
    public void disconnect() {
        if (connected) {
            System.out.println("Oracle接続を切断します");
            connected = false;
        }
    }

    @Override
    public void executeQuery(String query) {
        if (connected) {
            System.out.println("Oracleクエリ実行: " + query);
        } else {
            System.out.println("エラー: データベースに接続されていません");
        }
    }

    @Override
    public boolean isConnected() {
        return connected;
    }
}

class DatabaseManager {
    public static void testConnection(DatabaseConnection connection) {
        connection.connect();
        if (connection.isConnected()) {
            connection.executeQuery("SELECT * FROM users");
        }
        connection.disconnect();
    }
}

public class Main {
    public static void main(String[] args) {
        DatabaseConnection[] connections = {
            new MySQLConnection("localhost", "mydb"),
            new OracleConnection("oracle-server", "oracledb")
        };

        for (DatabaseConnection connection : connections) {
            DatabaseManager.testConnection(connection);
            System.out.println("---");
        }
    }
}

上級問題 解答例

上級問題1: プラグインシステムの設計 解答例

import java.util.ArrayList;
import java.util.List;

interface Plugin {
    String getName();
    String getVersion();
    void initialize();
    void execute(String text);
    void cleanup();
}

abstract class TextEditor {
    protected String currentText;
    protected List plugins;

    public TextEditor() {
        this.plugins = new ArrayList<>();
        this.currentText = "";
    }

    public void addPlugin(Plugin plugin) {
        plugins.add(plugin);
        plugin.initialize();
        System.out.println("プラグインを追加: " + plugin.getName() + " v" + plugin.getVersion());
    }

    public void setText(String text) {
        this.currentText = text;
        System.out.println("テキストを設定: " + text);
        applyPlugins();
    }

    protected void applyPlugins() {
        for (Plugin plugin : plugins) {
            plugin.execute(currentText);
        }
    }

    public void removePlugin(Plugin plugin) {
        if (plugins.remove(plugin)) {
            plugin.cleanup();
            System.out.println("プラグインを削除: " + plugin.getName());
        }
    }

    public abstract void display();
}

class SpellCheckPlugin implements Plugin {
    @Override
    public String getName() {
        return "スペルチェックプラグイン";
    }

    @Override
    public String getVersion() {
        return "1.0";
    }

    @Override
    public void initialize() {
        System.out.println("スペルチェック辞書を読み込み中...");
    }

    @Override
    public void execute(String text) {
        System.out.println("スペルチェックを実行: '" + text + "'");
        // 簡易的なスペルチェック
        if (text.toLowerCase().contains("erorr")) {
            System.out.println("  警告: 'erorr'は'error'の誤りかもしれません");
        }
    }

    @Override
    public void cleanup() {
        System.out.println("スペルチェック辞書を解放...");
    }
}

class FormatPlugin implements Plugin {
    @Override
    public String getName() {
        return "フォーマットプラグイン";
    }

    @Override
    public String getVersion() {
        return "1.2";
    }

    @Override
    public void initialize() {
        System.out.println("フォーマット設定を読み込み中...");
    }

    @Override
    public void execute(String text) {
        System.out.println("テキストフォーマットを適用: '" + text + "'");
        // 簡易的なフォーマット処理
        String formatted = text.trim().replaceAll("\\s+", " ");
        System.out.println("  フォーマット後: '" + formatted + "'");
    }

    @Override
    public void cleanup() {
        System.out.println("フォーマット設定を保存...");
    }
}

class StatisticsPlugin implements Plugin {
    @Override
    public String getName() {
        return "統計プラグイン";
    }

    @Override
    public String getVersion() {
        return "2.1";
    }

    @Override
    public void initialize() {
        System.out.println("統計モジュールを初期化中...");
    }

    @Override
    public void execute(String text) {
        System.out.println("テキスト統計を計算: '" + text + "'");
        int charCount = text.length();
        int wordCount = text.trim().isEmpty() ? 0 : text.trim().split("\\s+").length;
        int lineCount = text.split("\n").length;

        System.out.println("  文字数: " + charCount);
        System.out.println("  単語数: " + wordCount);
        System.out.println("  行数: " + lineCount);
    }

    @Override
    public void cleanup() {
        System.out.println("統計データをクリア...");
    }
}

class AdvancedTextEditor extends TextEditor {
    private String title;

    public AdvancedTextEditor() {
        super();
        this.title = "高度なテキストエディタ";
    }

    @Override
    public void display() {
        System.out.println("=== " + title + " ===");
        System.out.println("現在のテキスト: " + currentText);
        System.out.println("登録済みプラグイン数: " + plugins.size());
        for (Plugin plugin : plugins) {
            System.out.println("  - " + plugin.getName() + " v" + plugin.getVersion());
        }
        System.out.println("===================");
    }

    public void showPluginInfo() {
        System.out.println("=== プラグイン情報 ===");
        for (Plugin plugin : plugins) {
            System.out.println("名前: " + plugin.getName());
            System.out.println("バージョン: " + plugin.getVersion());
            System.out.println("---");
        }
    }
}

public class PluginSystem {
    public static void main(String[] args) {
        AdvancedTextEditor editor = new AdvancedTextEditor();

        // プラグインを追加
        editor.addPlugin(new SpellCheckPlugin());
        editor.addPlugin(new FormatPlugin());
        editor.addPlugin(new StatisticsPlugin());

        System.out.println();

        // テキストを設定(プラグインが自動実行)
        editor.setText("Hello   World! This is a sample text with erorr.");
        System.out.println();

        editor.setText("Another\nmultiline\ntext for testing.");
        System.out.println();

        // エディタ情報を表示
        editor.display();
        System.out.println();

        // プラグイン情報を表示
        editor.showPluginInfo();

        System.out.println();

        // プラグインを削除
        editor.removePlugin(new SpellCheckPlugin() {
            @Override
            public String getName() { return "スペルチェックプラグイン"; }
            @Override
            public String getVersion() { return "1.0"; }
            @Override
            public void initialize() {}
            @Override
            public void execute(String text) {}
            @Override
            public void cleanup() {}
        });

        System.out.println();
        editor.display();
    }
}

上級問題2: ストラテジーパターンの実装 解答例

import java.util.ArrayList;
import java.util.List;

interface PaymentStrategy {
    boolean pay(double amount);
    String getPaymentDetails();
    default boolean validateAmount(double amount) {
        if (amount <= 0) {
            System.out.println("エラー: 支払い金額は0より大きい必要があります");
            return false;
        }
        return true;
    }
}

class ShoppingCart {
    private List items;
    private PaymentStrategy paymentStrategy;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(String item) {
        items.add(item);
        System.out.println("商品を追加: " + item);
    }

    public void removeItem(String item) {
        if (items.remove(item)) {
            System.out.println("商品を削除: " + item);
        }
    }

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
        System.out.println("支払い方法を設定: " + strategy.getPaymentDetails());
    }

    public boolean checkout(double amount) {
        if (paymentStrategy == null) {
            System.out.println("エラー: 支払い方法が設定されていません");
            return false;
        }

        System.out.println("\n=== チェックアウト処理 ===");
        System.out.println("カート内の商品: " + items);
        System.out.println("合計金額: ¥" + String.format("%,.0f", amount));
        System.out.println("支払い方法: " + paymentStrategy.getPaymentDetails());

        boolean success = paymentStrategy.pay(amount);

        if (success) {
            System.out.println("✅ 注文が完了しました");
            items.clear(); // カートを空にする
        } else {
            System.out.println("❌ 支払いに失敗しました");
        }

        return success;
    }

    public void showCart() {
        System.out.println("=== ショッピングカート ===");
        if (items.isEmpty()) {
            System.out.println("カートは空です");
        } else {
            for (int i = 0; i < items.size(); i++) {
                System.out.println((i + 1) + ". " + items.get(i));
            }
        }
    }
}

class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    private String cardHolder;
    private String maskedCardNumber;

    public CreditCardPayment(String cardNumber, String cardHolder) {
        this.cardNumber = cardNumber;
        this.cardHolder = cardHolder;
        this.maskedCardNumber = maskCardNumber(cardNumber);
    }

    @Override
    public boolean pay(double amount) {
        if (!validateAmount(amount)) {
            return false;
        }

        System.out.println("💳 クレジットカード決済を処理中...");
        System.out.println("   カード名義: " + cardHolder);
        System.out.println("   カード番号: " + maskedCardNumber);
        System.out.println("   決済金額: ¥" + String.format("%,.0f", amount));

        // 簡易的な検証
        if (cardNumber.length() != 16) {
            System.out.println("   エラー: 無効なカード番号です");
            return false;
        }

        System.out.println("   ✅ 決済が承認されました");
        return true;
    }

    @Override
    public String getPaymentDetails() {
        return "クレジットカード (" + maskedCardNumber + ")";
    }

    private String maskCardNumber(String cardNumber) {
        return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public boolean pay(double amount) {
        if (!validateAmount(amount)) {
            return false;
        }

        System.out.println("🔵 PayPal決済を処理中...");
        System.out.println("   PayPalアカウント: " + email);
        System.out.println("   決済金額: ¥" + String.format("%,.0f", amount));

        // 簡易的な検証
        if (!email.contains("@")) {
            System.out.println("   エラー: 無効なメールアドレスです");
            return false;
        }

        System.out.println("   ✅ PayPal決済が完了しました");
        return true;
    }

    @Override
    public String getPaymentDetails() {
        return "PayPal (" + email + ")";
    }
}

class BitcoinPayment implements PaymentStrategy {
    private String walletAddress;

    public BitcoinPayment(String walletAddress) {
        this.walletAddress = walletAddress;
    }

    @Override
    public boolean pay(double amount) {
        if (!validateAmount(amount)) {
            return false;
        }

        System.out.println("₿ ビットコイン決済を処理中...");
        System.out.println("   ウォレットアドレス: " + walletAddress);
        System.out.println("   決済金額: ¥" + String.format("%,.0f", amount));

        // ビットコインへの変換(簡易版)
        double btcAmount = amount / 5000000.0; // 仮想レート
        System.out.println("   ビットコイン数量: " + String.format("%.8f", btcAmount) + " BTC");

        // 簡易的な検証
        if (walletAddress.length() < 26 || walletAddress.length() > 35) {
            System.out.println("   エラー: 無効なウォレットアドレスです");
            return false;
        }

        System.out.println("   ✅ ビットコイン送信が確認されました");
        return true;
    }

    @Override
    public String getPaymentDetails() {
        return "ビットコイン (" + walletAddress.substring(0, 8) + "..." + walletAddress.substring(walletAddress.length() - 8) + ")";
    }
}

public class StrategyPatternDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // 商品を追加
        cart.addItem("MacBook Pro 16インチ");
        cart.addItem("Magic Mouse");
        cart.addItem("USB-Cケーブル");

        System.out.println();
        cart.showCart();
        System.out.println();

        double totalAmount = 328000; // 合計金額

        // 支払い方法を切り替えてテスト
        PaymentStrategy[] strategies = {
            new CreditCardPayment("1234567812345678", "山田太郎"),
            new PayPalPayment("yamada@example.com"),
            new BitcoinPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
        };

        for (PaymentStrategy strategy : strategies) {
            cart.setPaymentStrategy(strategy);
            boolean success = cart.checkout(totalAmount);
            System.out.println("支払い結果: " + (success ? "✅ 成功" : "❌ 失敗"));
            System.out.println("=".repeat(50) + "\n");

            // 次のテスト用に商品を再追加
            if (success) {
                cart.addItem("新しい商品");
            }
        }

        // 無効な支払い方法のテスト
        System.out.println("=== エラーテスト ===");
        PaymentStrategy invalidCard = new CreditCardPayment("1234", "テスト");
        cart.setPaymentStrategy(invalidCard);
        cart.checkout(totalAmount);
    }
}

上級問題3: オブザーバーパターンの実装 解答例

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(float temperature, float humidity, float pressure);
    String getObserverName();
}

interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

class WeatherData implements Subject {
    private List observers;
    private float temperature;
    private float humidity;
    private float pressure;
    private boolean changed;

    public WeatherData() {
        this.observers = new ArrayList<>();
        this.changed = false;
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    private void measurementsChanged() {
        setChanged();
        notifyObservers();
    }

    private void setChanged() {
        this.changed = true;
    }

    private void clearChanged() {
        this.changed = false;
    }

    public boolean hasChanged() {
        return changed;
    }

    // Subjectインターフェースのメソッド実装
    @Override
    public void registerObserver(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
            System.out.println("📋 オブザーバーを登録: " + observer.getObserverName());
        }
    }

    @Override
    public void removeObserver(Observer observer) {
        if (observers.remove(observer)) {
            System.out.println("🗑️ オブザーバーを削除: " + observer.getObserverName());
        }
    }

    @Override
    public void notifyObservers() {
        if (hasChanged()) {
            for (Observer observer : observers) {
                observer.update(temperature, humidity, pressure);
            }
            clearChanged();
        }
    }

    // 気象データのゲッター
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    @Override
    public String getObserverName() {
        return "現在の気象状況表示";
    }

    public void display() {
        System.out.println("🌡️ 現在の気象状況:");
        System.out.println("   温度: " + temperature + "°C");
        System.out.println("   湿度: " + humidity + "%");
        System.out.println("   体感: " + getFeeling());
    }

    private String getFeeling() {
        if (temperature > 30) return "とても暑い";
        if (temperature > 25) return "暑い";
        if (temperature > 20) return "快適";
        if (temperature > 10) return "涼しい";
        return "寒い";
    }

    public void unsubscribe() {
        weatherData.removeObserver(this);
    }
}

class StatisticsDisplay implements Observer {
    private List temperatureReadings;
    private List humidityReadings;
    private List pressureReadings;
    private Subject weatherData;

    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        this.temperatureReadings = new ArrayList<>();
        this.humidityReadings = new ArrayList<>();
        this.pressureReadings = new ArrayList<>();
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        temperatureReadings.add(temperature);
        humidityReadings.add(humidity);
        pressureReadings.add(pressure);
        display();
    }

    @Override
    public String getObserverName() {
        return "気象統計表示";
    }

    public void display() {
        System.out.println("📊 気象統計:");
        System.out.println("   温度 - 平均: " + String.format("%.1f", calculateAverage(temperatureReadings)) + 
                          "°C, 最大: " + String.format("%.1f", calculateMax(temperatureReadings)) + 
                          "°C, 最小: " + String.format("%.1f", calculateMin(temperatureReadings)) + "°C");
        System.out.println("   湿度 - 平均: " + String.format("%.1f", calculateAverage(humidityReadings)) + "%");
        System.out.println("   気圧 - 平均: " + String.format("%.1f", calculateAverage(pressureReadings)) + "hPa");
        System.out.println("   記録数: " + temperatureReadings.size() + "回");
    }

    private float calculateAverage(List readings) {
        if (readings.isEmpty()) return 0;
        float sum = 0;
        for (float reading : readings) {
            sum += reading;
        }
        return sum / readings.size();
    }

    private float calculateMax(List readings) {
        if (readings.isEmpty()) return 0;
        float max = readings.get(0);
        for (float reading : readings) {
            if (reading > max) max = reading;
        }
        return max;
    }

    private float calculateMin(List readings) {
        if (readings.isEmpty()) return 0;
        float min = readings.get(0);
        for (float reading : readings) {
            if (reading < min) min = reading;
        }
        return min;
    }

    public void unsubscribe() {
        weatherData.removeObserver(this);
    }
}

class ForecastDisplay implements Observer {
    private float lastPressure;
    private Subject weatherData;

    public ForecastDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        this.lastPressure = 1013.0f; // 標準気圧
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        String forecast = calculateForecast(pressure);
        display(forecast);
        lastPressure = pressure;
    }

    @Override
    public String getObserverName() {
        return "天気予報表示";
    }

    private String calculateForecast(float currentPressure) {
        float pressureChange = currentPressure - lastPressure;

        if (pressureChange > 1.0) {
            return "天気は改善傾向です";
        } else if (pressureChange < -1.0) {
            return "天気は悪化傾向です";
        } else {
            return "天気はほぼ変わらないでしょう";
        }
    }

    public void display(String forecast) {
        System.out.println("🔮 天気予報:");
        System.out.println("   " + forecast);
        System.out.println("   現在の気圧: " + lastPressure + "hPa");
    }

    public void unsubscribe() {
        weatherData.removeObserver(this);
    }
}

class WeatherStation {
    private WeatherData weatherData;

    public WeatherStation() {
        this.weatherData = new WeatherData();
    }

    public void simulateWeatherChanges() {
        System.out.println("🚀 気象観測所: 天気変化のシミュレーションを開始します\n");

        // オブザーバーを登録
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        System.out.println();

        // 天気データの変更をシミュレート
        System.out.println("=== 観測データ1 ===");
        weatherData.setMeasurements(25.0f, 65.0f, 1013.0f);

        System.out.println("\n=== 観測データ2 ===");
        weatherData.setMeasurements(22.0f, 70.0f, 1012.0f);

        System.out.println("\n=== 観測データ3 ===");
        weatherData.setMeasurements(20.0f, 75.0f, 1011.0f);

        System.out.println("\n=== 観測データ4 ===");
        weatherData.setMeasurements(18.0f, 80.0f, 1010.0f);

        System.out.println("\n=== オブザーバー削除テスト ===");
        // 一部のオブザーバーを削除
        forecastDisplay.unsubscribe();

        System.out.println("\n=== 観測データ5 (予報表示なし) ===");
        weatherData.setMeasurements(16.0f, 85.0f, 1009.0f);

        System.out.println("\n=== 最終統計 ===");
        statisticsDisplay.display();
    }
}

public class ObserverPatternDemo {
    public static void main(String[] args) {
        WeatherStation station = new WeatherStation();
        station.simulateWeatherChanges();

        System.out.println("\n" + "=".repeat(50));
        System.out.println("オブザーバーパターンの特徴:");
        System.out.println("✅ 観測対象(Subject)と観測者(Observer)の疎結合");
        System.out.println("✅ 新しいObserverの追加が容易");
        System.out.println("✅ 実行時のObserverの登録・解除が可能");
        System.out.println("✅ データ変更時の自動通知");
    }
}