読み込み中...

JavaでImplementsをマスターするたったの10ステップ

JavaでImplementsを理解しよう Java
この記事は約25分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、Javaでimplementsキーワードを使いこなすことができるようになります。

Javaでよく使われるimplementsとは何か、どのような場面で必要なのか、そして実際にどのように使えばいいのか。

この記事ではこれらを10のステップと詳細なサンプルコードを通して徹底的に解説します。

●Javaとimplementsの基本概念

○Javaとは

Javaは、1995年にSun Microsystems(現在はOracle Corporationに買収されています)によって開発されたプログラミング言語です。

一度書いたプログラムが、さまざまなプラットフォームで動作するという特徴があります。

これは、「Write Once, Run Anywhere」とも表現されます。Javaはウェブアプリケーション、スマートフォンアプリ、組み込みシステムなど、多様な分野で使用されています。

○implementsキーワードとは

Javaでは、implementsキーワードは、あるクラスが特定のインターフェースを実装する際に使用されます。

インターフェースとは、抽象メソッド(具体的な処理内容のないメソッド)の集合であり、これを実装(implements)するクラスは、そのインターフェースで定義された抽象メソッドを全て具体的に処理する必要があります。

インターフェースの役割は、実装すべきメソッドの「形」を定義することです。

そのため、implementsキーワードを使ってインターフェースを実装すると、その「形」に従ってクラスにメソッドを追加する必要があります。

例えば、Runnableインターフェースを実装するには、その中で定義されているrunメソッドを必ずオーバーライド(新しく定義し直すこと)しなければなりません。

このようにして、Javaではimplementsキーワードを使用することで、特定の機能を持つクラスを簡単に作成することができます。

●implementsの詳細な使い方

ここでimplementsの詳細な使い方について解説していきます。

Javaでよく使われるこのキーワード、実際にはどう使うのでしょうか。

具体的なサンプルコードを通じて、その全貌を解明していきましょう。

○サンプルコード1:単純なインターフェースの実装

最初に、一番基本的な形でインターフェースを実装してみましょう。

このサンプルでは、Animalインターフェースを作成し、Dogクラスでそれを実装します。

// Animalインターフェース
interface Animal {
  // 鳴くという動作を抽象メソッドとして定義
  void bark();
}

// DogクラスでAnimalインターフェースを実装
class Dog implements Animal {
  // barkメソッドをオーバーライド
  public void bark() {
    System.out.println("ワンワン");
  }
}

public class Main {
  public static void main(String[] args) {
    Dog myDog = new Dog();
    myDog.bark();  // ワンワンと出力
  }
}

このコードでは、Animalインターフェースにbarkというメソッドを定義しました。

そして、DogクラスでこのAnimalインターフェースを実装(implements)しています。

barkメソッドは、犬が「ワンワン」と鳴く動作をコンソールに出力します。

このコードを実行すると、犬が「ワンワン」と鳴く動作がコンソールに出力されます。

○サンプルコード2:複数のインターフェースを実装

Javaでは一つのクラスが複数のインターフェースを実装することも可能です。

このサンプルでは、Animalという別のインターフェース、Petを作成し、Dogクラスで両方を実装してみます。

// Animalインターフェース
interface Animal {
  void bark();
}

// Petインターフェース
interface Pet {
  void beFriendly();
}

// DogクラスでAnimalとPetの両方を実装
class Dog implements Animal, Pet {
  // Animalのbarkメソッドをオーバーライド
  public void bark() {
    System.out.println("ワンワン");
  }

  // PetのbeFriendlyメソッドをオーバーライド
  public void beFriendly() {
    System.out.println("しっぽを振る");
  }
}

public class Main {
  public static void main(String[] args) {
    Dog myDog = new Dog();
    myDog.bark();  // ワンワンと出力
    myDog.beFriendly();  // しっぽを振ると出力
  }
}

このコードでは、AnimalインターフェースとPetインターフェースの2つをDogクラスで実装しています。

それぞれのインターフェースで定義されたbarkメソッドとbeFriendlyメソッドをオーバーライドしています。

このコードを実行すると、犬が「ワンワン」と鳴く動作と、犬が「しっぽを振る」という動作がコンソールに出力されます。

○サンプルコード3:インターフェースのメソッドをオーバーライド

Javaでインターフェースを実装する際、そのメソッドは必ずオーバーライドする必要があります。

ここではAnimalインターフェースを拡張し、runという新たなメソッドを追加します。

その後で、Dogクラスでこの新しいメソッドも含めてオーバーライドしてみましょう。

// Animalインターフェースにrunメソッドを追加
interface Animal {
  void bark();
  void run();
}

// DogクラスでAnimalインターフェースを実装
class Dog implements Animal {
  // barkメソッドをオーバーライド
  public void bark() {
    System.out.println("ワンワン");
  }

  // runメソッドをオーバーライド
  public void run() {
    System.out.println("走る");
  }
}

public class Main {
  public static void main(String[] args) {
    Dog myDog = new Dog();
    myDog.bark();  // ワンワンと出力
    myDog.run();  // 走ると出力
  }
}

このコードでは、Animalインターフェースに新たにrunメソッドが追加されています。

このrunメソッドもDogクラスでしっかりとオーバーライドしている点に注意してください。

この場合のrunメソッドは犬が「走る」という動作をコンソールに出力します。

このコードを実行すると、Dogクラスのオブジェクトが「ワンワン」と吠えるとともに、「走る」という動作をすることがコンソールで確認できます。

○サンプルコード4:抽象クラスとの違い

Javaには抽象クラスという概念もありますが、インターフェースとは何が違うのでしょうか。

主な違いは「状態を持つことができるかどうか」です。

抽象クラスは状態(フィールド変数)を持つことができますが、インターフェースはメソッドの定義のみが可能です。

// 抽象クラスAnimalAbstractの定義
abstract class AnimalAbstract {
  String name; // 状態を表すフィールド変数

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

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

// DogクラスでAnimalAbstractを継承
class Dog extends AnimalAbstract {
  Dog(String name) {
    super(name);
  }

  // barkメソッドをオーバーライド
  public void bark() {
    System.out.println(name + "がワンワンと吠える");
  }
}

public class Main {
  public static void main(String[] args) {
    Dog myDog = new Dog("ポチ");
    myDog.bark();  // ポチがワンワンと吠えると出力
  }
}

このコードでは、AnimalAbstractという抽象クラスを作成しています。

この抽象クラスにはnameというフィールド変数が存在します。

その後で、Dogクラスがこの抽象クラスを継承(extends)しています。

このコードを実行すると、DogクラスのオブジェクトmyDogが「ポチがワンワンと吠える」という動作とともに、その名前も出力される点に注目してください。

○サンプルコード5:デフォルトメソッドの利用

Java 8以降、インターフェースにもメソッドの実装が可能になりました。これをデフォルトメソッドと呼びます。

デフォルトメソッドは、新しくメソッドがインターフェースに追加されても、既存のクラスに影響を与えないように設計されています。

早速、サンプルコードを通じてデフォルトメソッドの使い方を解説します。

// Animalインターフェースにデフォルトメソッドを追加
interface Animal {
  void bark();

  // デフォルトメソッドの追加
  default void run() {
    System.out.println("動物が走る");
  }
}

// DogクラスでAnimalインターフェースを実装
class Dog implements Animal {
  public void bark() {
    System.out.println("ワンワン");
  }
}

public class Main {
  public static void main(String[] args) {
    Dog myDog = new Dog();
    myDog.bark();  // ワンワンと出力
    myDog.run();   // 動物が走ると出力
  }
}

このJavaコードで目を引くのは、Animalインターフェース内にrunメソッドがデフォルトメソッドとして定義されている点です。

デフォルトメソッドは、defaultキーワードを使用してメソッドに実装を持たせられます。

このデフォルトメソッドを使用すると、それを実装したDogクラスでも自動的にrunメソッドが利用できるようになります。

実行結果としては、「ワンワン」と「動物が走る」がコンソールに出力されます。

●implementsの応用例

Javaのimplementsキーワードとインターフェースは、非常に多面的で柔軟な設計が可能です。

ここでは、実際によく使われる応用例をサンプルコードと共に詳しく解説します。

○サンプルコード6:コレクションフレームワークでの利用例

Javaのコレクションフレームワークでよく使われるインターフェースがあります。特に、ListSetMapなどが代表的です。

このようなインターフェースは、具象クラス(例:ArrayList, HashSet)で実装されています。

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

public class SampleList {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

このサンプルコードは、Listインターフェースとその具象クラスであるArrayListを使用しています。

実行すると、Apple、Banana、Cherryが順番に出力されます。

○サンプルコード7:イベントリスナーとしての使用例

Javaにおいてイベント処理を行う際にも、implementsが使われます。

イベントリスナーと呼ばれるインターフェースがあり、これを実装することで特定のイベントに対する処理を記述することができます。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;

public class ButtonExample implements ActionListener {
    JButton button;

    public ButtonExample() {
        JFrame frame = new JFrame();
        button = new JButton("Click Me");
        button.addActionListener(this);
        frame.add(button);
        frame.setSize(200, 100);
        frame.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        button.setText("Clicked!");
    }

    public static void main(String[] args) {
        new ButtonExample();
    }
}

このサンプルコードでは、JavaのSwingライブラリを使い、ボタンのクリックイベントを処理しています。

ActionListenerインターフェースをimplementsして、そのactionPerformedメソッドをオーバーライドしています。

ボタンをクリックすると、ボタンのラベルが「Clicked!」に変わります。

○サンプルコード8:ストラテジーパターンにおける利用例

デザインパターンの一つであるストラテジーパターンは、具体的な処理をカプセル化して、それを簡単に切り替えられるようにする方法です。

このストラテジーパターンも、implementsを用いてJavaで実装することが一般的です。

ストラテジーパターンでは、アルゴリズム(処理)をインターフェースとして定義します。

その後、このインターフェースをimplementsする形で具体的なアルゴリズムを表現するクラスを作成します。

// Strategy インターフェースの定義
interface PaymentStrategy {
    void pay(int amount);
}

// 具体的な戦略(アルゴリズム)1:クレジットカード
class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("クレジットカードで" + amount + "円支払いました。");
    }
}

// 具体的な戦略(アルゴリズム)2:現金
class CashPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("現金で" + amount + "円支払いました。");
    }
}

// 実行クラス
public class StrategyPatternExample {
    public static void main(String[] args) {
        PaymentStrategy strategy1 = new CreditCardPayment();
        PaymentStrategy strategy2 = new CashPayment();

        strategy1.pay(3000);
        strategy2.pay(5000);
    }
}

このサンプルコードでは、PaymentStrategyという名前のインターフェースを作成しています。

このインターフェースにはpayというメソッドが定義されています。

CreditCardPaymentCashPaymentは、このインターフェースをimplementsしています。

それぞれのクラスでpayメソッドが異なる処理(支払い方法)を持っています。

このプログラムを実行すると、出力としては「クレジットカードで3000円支払いました。」と「現金で5000円支払いました。」がそれぞれ表示されます。

○サンプルコード9:ユーザー定義インターフェースの作成

Javaでは、独自にインターフェースを定義して使用することが可能です。

この機能を利用して、特定のビジネスロジックや規則に合わせた処理を行うことができます。

// 自作のインターフェース
interface Greeting {
    void sayHello(String name);
}

// インターフェースを実装したクラス
class EnglishGreeting implements Greeting {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

// インターフェースを実装した別のクラス
class JapaneseGreeting implements Greeting {
    @Override
    public void sayHello(String name) {
        System.out.println("こんにちは、" + name + "さん!");
    }
}

// 実行クラス
public class CustomInterfaceExample {
    public static void main(String[] args) {
        Greeting english = new EnglishGreeting();
        Greeting japanese = new JapaneseGreeting();

        english.sayHello("John");
        japanese.sayHello("太郎");
    }
}

このサンプルコードでは、Greetingという自作のインターフェースを定義しています。

そして、そのインターフェースをimplementsして、EnglishGreetingJapaneseGreetingという2つのクラスを作成しています。

プログラムを実行すると、「Hello, John!」と「こんにちは、太郎さん!」がそれぞれ出力されます。

○サンプルコード10:implementsとジェネリクス

ジェネリクスは、型の安全性を高めるためにJavaに導入されました。

これを使うと、コンパイル時に型チェックが行われ、実行時の型キャストの必要が減少します。

特に、ジェネリクスとimplementsを組み合わせることで、非常に柔軟で再利用可能なコードを作成することができます。

ジェネリクスを用いたインターフェースの実装例を紹介します。

// ジェネリクスを持つインターフェース
interface Storage<T> {
    void add(T item);
    T get();
}

// Stringを保存するクラス
class StringStorage implements Storage<String> {

    private String data;

    @Override
    public void add(String item) {
        this.data = item;
    }

    @Override
    public String get() {
        return data;
    }
}

// 実行クラス
public class GenericsExample {
    public static void main(String[] args) {
        Storage<String> storage = new StringStorage();
        storage.add("Java");
        System.out.println(storage.get());  // 出力結果: Java
    }
}

このサンプルコードでは、Storageという名前のジェネリクスを持つインターフェースを定義しています。

このインターフェースは、addgetという2つのメソッドを持っています。

StringStorageクラスは、このインターフェースをimplementsし、具体的にString型のデータを保存する動作を持っています。

上記の実行クラスGenericsExampleを実行すると、「Java」という文字列が出力されます。

これは、StringStorageクラスのaddメソッドでデータを保存し、getメソッドで取得しているからです。

ジェネリクスを使用すると、同じロジックをさまざまな型のデータに適用できるため、コードの再利用性が高まります。

また、implementsを使用してジェネリクスを持つインターフェースを実装することで、具体的な型のデータを扱うクラスを柔軟に作成することができます。

●注意点と対処法

Javaでimplementsキーワードを用いてインターフェースを実装する際には、多くの注意点と対処法が存在します。

これらに気を付けながらプログラムを作成することで、より効率的でバグの少ないコードが書けます。

○メソッドのシグネチャに注意

Javaのインターフェースを実装するには、インターフェースで定義されているメソッドのシグネチャ(メソッド名、引数の型、戻り値の型)を正確にオーバーライドする必要があります。

シグネチャが一致しないと、コンパイルエラーが発生します。

// インターフェースの定義
interface Display {
    void show(String msg);
}

// インターフェースを正しく実装していないクラス
class WrongDisplay implements Display {
    // 引数がint型になっているため、シグネチャが一致しない
    public void show(int msg) {
        System.out.println("Message is: " + msg);
    }
}

上記のコード例では、WrongDisplayクラスがDisplayインターフェースを正確に実装していないため、コンパイルエラーが出ます。

これを避けるためには、showメソッドの引数をString型に修正する必要があります。

このようにメソッドのシグネチャが一致しているかどうかは、コンパイル時に検証されるため、開発者はこの点を注意深く確認する必要があります。

○アクセス修飾子の注意点

インターフェースで定義されるメソッドは、暗黙的にpublicアクセス修飾子が設定されています。

そのため、インターフェースを実装するクラスでも、そのメソッドはpublicでなければなりません。

// インターフェースの定義
interface Animal {
    void speak();
}

// インターフェースを正しく実装していないクラス
class Dog implements Animal {
    // アクセス修飾子がない(パッケージプライベート)ため、コンパイルエラー
    void speak() {
        System.out.println("Woof!");
    }
}

この例のDogクラスは、speakメソッドにアクセス修飾子が設定されていないため、コンパイルエラーが発生します。

この問題を解決するには、speakメソッドにpublicアクセス修飾子を追加する必要があります。

○ダイヤモンド問題とその対処法

Javaでは、一つのクラスが複数のインターフェースを実装できるので、ダイヤモンド問題が発生する可能性があります。

具体的には、複数のインターフェースが同じメソッドを持っている場合に問題が発生します。

// 2つの異なるインターフェース
interface Engine {
    void start();
}

interface Vehicle {
    void start();
}

// 両方のインターフェースを実装
class Car implements Engine, Vehicle {
    // どちらのstartメソッドもここで実装
    public void start() {
        System.out.println("Car started");
    }
}

// 実行クラス
public class DiamondExample {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.start();  // 出力: "Car started"
    }
}

上記のCarクラスでは、EngineVehicleという2つのインターフェースを実装していますが、両方のインターフェースでstartメソッドが定義されているため、Carクラス内で一度このメソッドを実装すれば、両方のstartメソッドに対する実装とみなされます。

●カスタマイズ方法

Javaでimplementsを用いたインターフェースの実装を理解し、基本的な使い方や注意点を把握したら、次はさまざまなカスタマイズ方法について学びましょう。

○インターフェースの拡張

Javaでは、既存のインターフェースを拡張して新しいインターフェースを定義することができます。

この技術は、継承の一形態とも言えます。

// 元となるインターフェース
interface Shape {
    void draw();
}

// Shapeを拡張した新しいインターフェース
interface ColoredShape extends Shape {
    void setColor(String color);
}

// ColoredShapeを実装するクラス
class Square implements ColoredShape {
    private String color;

    // drawメソッドをオーバーライド
    public void draw() {
        System.out.println(color + "の四角形を描画します。");
    }

    // setColorメソッドをオーバーライド
    public void setColor(String color) {
        this.color = color;
    }
}

このコード例では、Shapeインターフェースにdrawメソッドがあり、これを拡張したColoredShapeインターフェースがsetColorメソッドを追加しています。

SquareクラスはColoredShapeを実装しているため、drawメソッドとsetColorメソッドの両方をオーバーライドしています。

このコードを実行する際に、SquareオブジェクトのsetColordrawメソッドを呼び出すと、「赤の四角形を描画します。」のように出力されます。

○デフォルトメソッドのオーバーライド

Java 8以降、インターフェース内でメソッドに実装(デフォルトメソッド)を提供することができます。

しかし、これをそのまま使うのではなく、必要に応じてオーバーライドすることもできます。

// デフォルトメソッドを持つインターフェース
interface Greeter {
    default void greet() {
        System.out.println("Hello, world!");
    }
}

// Greeterを実装してデフォルトメソッドをオーバーライドするクラス
class JapaneseGreeter implements Greeter {
    // greetメソッドをオーバーライド
    public void greet() {
        System.out.println("こんにちは、世界!");
    }
}

このコード例では、Greeterインターフェースがデフォルトメソッドgreetを持っています。

JapaneseGreeterクラスはこのメソッドをオーバーライドして、日本語で挨拶を出力します。

このコードを実行すると、JapaneseGreeterオブジェクトのgreetメソッドを呼び出すと「こんにちは、世界!」と出力されます。

○自作インターフェースでの応用

Javaで自分自身が定義したインターフェースを使って、特定の機能や規約を強制することも可能です。

// 自作インターフェース
interface Calculator {
    int add(int a, int b);
}

// 自作インターフェースを実装するクラス
class SimpleCalculator implements Calculator {
    // addメソッドをオーバーライド
    public int add(int a, int b) {
        return a + b;
    }
}

このコードでは、Calculatorという自作インターフェースにaddメソッドを定義し、それをSimpleCalculatorクラスで実装しています。

このコードを実行すると、SimpleCalculatorオブジェクトのaddメソッドを用いて数値を加算できます。

例えば、add(3, 4)とすると、7が返されます。

まとめ

Javaでのimplementsキーワードの使い方について、基本的なコンセプトから詳細な使い方、注意点、そして応用テクニックまで幅広く解説してきました。

この記事を通じて、Javaプログラミングにおけるインターフェースの重要性や活用方法についての理解が深まったことと思います。

Javaのimplementsは、インターフェースを実装するための非常に強力な機能であり、設計段階での柔軟性や拡張性を確保するための鍵となる要素です。

特に、インターフェースを活用することで、コードの再利用性が高まり、メンテナンスも効率的に行えるようになります。

継続的な学習と実践を通じて、更なるスキル向上を目指しましょう。