
Javaのインターフェイスを実装するimplementsとは?
Javaのキーワードimplementsは、インターフェイスを実装する時に使うものです…紋切り型に言えばこうですが、どうにもピンとこない人が実は多いのではないのでしょうか?
「実装」というお堅い言葉が何を指しているのか、何を意味しているのかよく分からないな、とJavaを使い始めた時に私は思っていました。それに、そもそもクラスとは別にインターフェイスというものがなぜあるのか、その意味や使い方も良くわかっていませんでした。
簡単に言ってしまえば、インターフェイスとは「こういうことができる」という約束のようなものです。implements(=実装する)とは、その約束を守るために実際に行動する、というイメージだと言えば、多少はわかりやすいでしょうか。
この記事では、implementsの基本とインターフェイスの使い方について、インターフェイスの意味と一緒に初心者向けにお伝えします。
※この記事のサンプルは、Java 10の環境で動作確認しています。
1.implementsの構文
まずは、Javaでのimplementsの構文を一つずつ学びましょう。
1-1.インターフェイスを1つimplementsする
あるクラスに、インターフェイスを実装(=implements)するには以下のように書きます。クラス名の後ろにキーワードimplementsを書き、さらにその後ろにインターフェイス名を書くのですね。
class クラス名 implements インターフェイス名 { }
インターフェイスに抽象メソッドが定義されているなら、クラスではその抽象メソッドの内容を実際に作らなくてはなりません。そうしないとコンパイルエラーとなってしまいます。これを「抽象メソッドの実装」と呼びます。
class クラス名 implements インターフェイス名 { インターフェイスの抽象メソッド() { 抽象メソッドの実装; } }
なお、抽象クラス(abstract class)にインターフェイスを実装するなら、インターフェイスの抽象メソッドの実装は必須ではありません。
それでも、抽象クラスを継承して具象クラス(abstractではないクラス)を作る際は、インターフェイスの抽象メソッドの実装が必要となります。
abstract class クラス名 implements インターフェイス名 { // 抽象クラスなら、インターフェイスの抽象メソッドは実装しなくてもいい }
1-2.インターフェイスを複数implementsする
Javaでは、一つのクラスにはインターフェイスを複数実装できます。
インターフェイスを複数実装するには、implementsの後ろに、実装したいインターフェイスをカンマ(,)で繋げてください。この場合、クラスでは実装したいインターフェイスにある全ての抽象メソッドの内容を作らなければなりませんよ。
class クラス名 implements インターフェイス名1, インターフェイス名2, ... { インターフェイス1の抽象メソッド() { 抽象メソッドの実装; } インターフェイス2の抽象メソッド() { 抽象メソッドの実装; } // 以下同様に、インターフェイスのメソッドを実装する }
2.implementsの例
implementsの練習として、Javaの標準APIにあるインターフェイスを実装したクラスを作ってみましょう。
この記事では、java.langにあるRunnableとAutoCloseableを実装してみます。これらのインターフェイスは両方とも、抽象メソッドを一つ持っています。
// java.lang.Runnableの宣言(JDK 10より) public interface Runnable { public abstract void run(); }
// java.lang.AutoCloseableの宣言(JDK 10より) public interface AutoCloseable { void close() throws Exception; }
これらのインターフェイスを実装したクラスは、例えば以下のようになります。
implementsの後ろにインターフェイス名があって、インターフェイスでの宣言そのままのメソッドがありますよね。
// インターフェイスを1つ実装したクラス class RunnableImpl implements Runnable { public void run() { System.out.println("RunnableImpl"); } }
// インターフェイスを2つ実装したクラス class ManyInterfaceImpl implements Runnable, AutoCloseable { public void run() { System.out.println("ManyInterfaceImpl.run()"); } public void close() { System.out.println("ManyInterfaceImpl.close()"); } }
ちなみに、これらの例でクラス名に~Implとあるのは、実際のプログラムでも良く見かけるルールです。
Implはご推察のとおりimplementsの略で、何かのインターフェイスを実装したクラスだよ、という意味です。
3.implementsが実現する3つのこと
3-1.クラスが複数の「顔」を持てるようになる
インターフェイスを実装したクラスは、インターフェイスで宣言された抽象メソッドを呼び出せるようになります。そして、implementsのさらにスゴイところは、クラスをそのインターフェイスの型としても扱えることです。
これは言葉だと少しわかりづらいので、実際のプログラムでどんな様子になるか見てみましょう。
// ① 実装したインターフェイスのメソッドを呼び出せるし、 ManyInterfaceImpl miImpl = new ManyInterfaceImpl(); miImpl.run(); miImpl.close(); // ②-1 Runnableとしても扱えるし、 Runnable runnable = new ManyInterfaceImpl(); runnable.run(); // ②-2 Closeableとしても扱える!! AutoCloseable closeable = new ManyInterfaceImpl(); closeable.close();
①はわかりやすいかと思います。newしたManyInterfaceImplのインスタンスを、型ManyInterfaceImplの変数に代入しています。これは普通のクラスの使い方ですよね。
そして、ManyInterfaceImplは二つのインターフェイスを実装しているので、それぞれのインターフェイスの抽象メソッドを呼び出せています。これも、クラスを見ればまぁそんなものかな、と思われるでしょう。
②-1~2は、それぞれのインターフェイスの型としても扱える、ということの例です。
newしたManyInterfaceImplのインスタンスは、実装しているインターフェイスの型への代入ができています。それに、インターフェイスとしてメソッドを呼び出せば、もちろん実装されたメソッドが実行されます。
つまり、一つのクラスではありますが、クラスそのものとしての「顔」以外にも、インターフェイスとしての二つの「顔」も同時に持っている…ということです。状況に応じて顔を使い分けることが、インターフェイスを実装したクラスではできるようになってしまうのです。
3-2.違うクラスを「同一視」できる
一つのインターフェイスを複数のクラスで実装しても、何も問題はありません。むしろそうできるということこそが、インターフェイスの大事な存在意義であるとすら言えるかもしれません。
先ほどの例でも、Runnableを両方のクラスで実装していましたよね。ですから、あの2つのクラスは両方ともRunnableとして同一視できるのです。
ということは、以下のようにプログラミングできることになります。
class ImplementsSample2 { public static void main(String[] args) { RunnableImpl rImpl = new RunnableImpl(); ManyInterfaceImpl miImpl = new ManyInterfaceImpl(); invokeRun(rImpl); invokeRun(miImpl); } static void invokeRun(Runnable runnable) { runnable.run(); } }
RunnableImplとManyInterfaceImplには、直接的なクラスの継承関係はありません。ですから、それぞれのクラスはまったく違うものです。まったくの他人同士ということですね。
でも、同じRunnableを実装しているので、両方ともRunnableだと見なせるのです。ですから、メソッドinvokeRunへの引数としてRunnableへのキャストをしなくても、そのまま指定できています。
つまり、クラスの継承関係上は全く関係のないクラス同士でも、同じインターフェイスを実装さえしていれば、同じモノとして扱えるということです。
一つのクラスが別のモノともみなせる、複数のクラスがインターフェイスを通じて同じモノとみなせるという考え方は、慣れないとちょっと難しいかもしれません。ですが、Javaやオブジェクト指向ではとても大事なことですので、時間がかかってもいいので、必ず理解しましょう。
3-3.クラスへ「役割」を必要なだけ追加できる
先述のとおり、クラスには複数のインターフェイスをimplementsできます。ということは、クラスには必要なだけインターフェイスを追加できる…すなわち、それぞれのインターフェイスが表す「役割」を追加できるのです。
それらの役割は、Javaのプログラミングではとても活用されます。特に、Javaの標準APIに存在する色々なインターフェイスは、自分のクラスに役割を追加して、JavaのAPIでどんどん活用するためにあると言ってもいいでしょう。
例で使ったAutoCloseableは「用が済んだら何かを勝手に閉じる」という役割を持つインターフェイスです。
「閉じる」ものは、概念的に閉じられるものなら何でもOKで、例えばファイルやネットワーク通信、データベース接続のようなものです。つまり、自分が持っている何かを「閉じる」ことができると言っているわけですね。
このインターフェイスを使うと「開いたものは必ず閉じる」という、プログラミングでは絶対に忘れてはならないことを、自分で作ったクラスに役割として簡単・確実に追加して、Javaに面倒を見てもらえるのです!!(※ご存知の方向けに言うならば、try-with-resource文のことです)
このようなJavaのプログラミングでよく見かけるインターフェイスは、Runnable、Callable、Comparable、Closeable、Serializable、Iterableなどでしょうか。これらのインターフェイス名が~ableになっているのは決して偶然ではなく、~できるという役割を表現しているからです。
4.【応用】インターフェイスに対してプログラミングする
ここまでで、implementsはインターフェイスを実装する時に使うことと、インターフェイスを使うことの便利さがお分かりいただけたでしょうか。
この章ではインターフェイスについて、もう少し進んだ話題をお伝えします。
プログラミングのベストプラクティスの一つに「インターフェイスに対してプログラミングする」というものがあります。
ここで言う“インターフェイス“とは、この記事でお伝えしてきたimplementsするインターフェイスや、抽象クラス・抽象メソッドなどを含めた、広い範囲での「約束」や「決め事」のようなものだと思ってください。
かいつまんで言えば、プログラムの設計部分と実装部分をきちんと分けて、変化に柔軟に対応できるようにする、という考え方です。つまり、設計部分を“インターフェイス“で表現し、実装部分を個々のクラスで表現するというスタイルです。
4-1.クラスを使ったプログラミング
「インターフェイスに対してプログラミングする」は、実際にJavaの色々なところで使われています。
例として、Javaの標準APIのインターフェイスにjava.util.List、java.util.Map、java.util.Setなどがあります。皆さんも、これらのインターフェイスとその実装クラス(ArrayListやHashMapなど)を、よく使っていると思います。
HashMapを使ったプログラムでありがちなのは、以下のようなものです。
void someMethod(HashMap map) { HashMapを使った何かの処理; }
引数の型として、HashMapを使っていますね。
「普通のプログラムじゃん」とか「これって何か駄目なの?」と思われた方も多いでしょう。
確かに、Javaのプログラムとしては問題ありませんので、コンパイルはできますし、実行もできます。
4-2.クラスをそのまま使うと融通が利かない
「インターフェイスに対してプログラミングする」ができていないと、融通が利かなかったり、同じことをしているプログラムが多くなりがちです。
前述のメソッドで、Mapの仲間であるLinkedHashMapやTreeMapを使う必要が出た場合は、それぞれのMapごとにメソッドをオーバーロードするのでしょうか。
// HashMap用のメソッドを… void someMethod(HashMap map) { ...; } // LinkedHashMap用にオーバーロードして… void someMethod(LinkedHashMap map) { ...; } // TreeMap用にもオーバーロードする void someMethod(TreeMap map) { ...; }
これでは切りがないですし、しかも元々の処理を複数のメソッドの間でコピー&ペーストしてしまいがちです。
やりたいことはまったく同じなのに、Mapを実装しているクラスが違うというだけで…。
4-3.インターフェイスを使ったプログラミング
ここで、以下のようにしてみます。引数の型を、クラスのHashMapから、インターフェイスのMapにしてみました。
こちらのメソッドだと、Mapを実装しているクラスなら、HashMapでもTreeMapでもLinkedHashMapでも、何でも同じように扱えます。
void someMethod(Map map) { Mapを使った何かの処理; }
さて、あなたのプログラムに本当に必要な変数・引数・戻り値の型は、HashMapですか? それともMapですか? この違いをしっかり考えたことはありますか?
ほとんどの場合で、キーと値をペアにして出し入れできる機能、すなわちインターフェイスのMapで十分なはずです。
これが「インターフェイスに対してプログラミングする」の一例です。インターフェイスを実装している実際のクラスを知らなくても、抽象的にプログラミングできることこそが、オブジェクト指向の強みなのです。
4-4.インターフェイスとは約束、インターフェイスを実装するとは約束を守ること
インターフェイスとは「このインターフェイスではこういうことができる」という約束です。前章でお伝えしたとおり、役割と言ってもいいでしょう。
インターフェイスを実装するとは、インターフェイスの約束や役割を守った、実際に何かの動作を行うクラスを作るということです。
そして、インターフェイスに対してプログラミングするとは、具体的な実装のされ方はわからないけれど、インターフェイスでの約束のとおりにインスタンスが動くことを信じてプログラミングをする、ということです。
これをずっと進めると、プログラムで必要な要件をインターフェイスですべて事前に明確にしておけば、インターフェイスだけでプログラムが作れるようになる、ということです。現実的には、なかなか難しいのですけれども。
もちろん、インターフェイスの機能を実装したクラスは必要です。そうでないと、実際には何も動かせません。処理の実装は、例えばプログラムで使っているフレームワークやデータベースなどにより、大きく違うものです。
でも、インターフェイスを使うことで、処理の流れと処理の実装をきれいに分けられるようになるのでは、と思えませんか?
処理で「やりたいこと」そのものは、実装されている方法が何であれ違いはないのではないか、とは思えませんか?
「やりたいこと」をインターフェイスでプログラミングし、「実際の動き」をクラスでプログラミングする。このようなプログラミングスタイルが「インターフェイスに対してプログラミングする」が目指している姿なのです。
5.まとめ
この記事では、Javaでインターフェイスを実装する時に使うキーワードimplementsと、インターフェイスの特徴を簡単にお伝えしました。
implementsされるインターフェイスは、クラスに一つだけではなく、複数個を一度に実装できます。インターフェイスはクラスの継承関係とは関係なく、必要に応じて機能を追加できる、プラグインのようなものです。
インターフェイスを実装したクラスは、そのインターフェイスそのものとしても振る舞えるようにもなるのです。
そして、インターフェイスに対してプログラミングをすることで、扱っているモノに対して高い抽象度でプログラミングできるのです。それにより、プログラムでやりたいことに集中しつつ、柔軟なプログラミングができるようになるのです。
Javaを勉強してクラスの考え方に慣れたら、次はぜひインターフェイスを使うことに慣れましょう。インターフェイスの考え方が分かってくると、ものすごく便利なものだと実感できると思います。インターフェイスは、有名な「デザインパターン」でも大変活用されていたりしますよ。
皆さんも、ぜひインターフェイスを活用したプログラミングの世界に入門してみませんか?
コメント