はじめに
この記事を読めば、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のコレクションフレームワークでよく使われるインターフェースがあります。特に、List
やSet
、Map
などが代表的です。
このようなインターフェースは、具象クラス(例: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
というメソッドが定義されています。
CreditCardPayment
とCashPayment
は、このインターフェースを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
して、EnglishGreeting
とJapaneseGreeting
という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
という名前のジェネリクスを持つインターフェースを定義しています。
このインターフェースは、add
とget
という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
クラスでは、Engine
とVehicle
という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
オブジェクトのsetColor
とdraw
メソッドを呼び出すと、「赤の四角形を描画します。」のように出力されます。
○デフォルトメソッドのオーバーライド
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
は、インターフェースを実装するための非常に強力な機能であり、設計段階での柔軟性や拡張性を確保するための鍵となる要素です。
特に、インターフェースを活用することで、コードの再利用性が高まり、メンテナンスも効率的に行えるようになります。
継続的な学習と実践を通じて、更なるスキル向上を目指しましょう。