Javaで処理を書く方法の1つにインターフェースがあります。しかし、クラスとインターフェースの違いがわからず、コードの書き方がよくわからない初学者の方もいるでしょう。また、チーム開発でインターフェースを活用したい方もコード設計や複数実装のもととなる基礎を押さえることは大切です。
そこで本記事は、Javaのインターフェースのメリットや基本・応用の書き方などを解説します。
目次
閉じる
1.Javaのインターフェースとは
Javaのプログラミングでは、開発を効率的に行うためにインターフェースを利用します。そこで、インターフェースの位置付けやAPIとの違いなどについて以下に解説します。
インターフェースの定義
インターフェース(interface)は、情報技術分野で使われる機器や通信方式、処理方法のことを指します。
例えば、電子機器のケーブルはインターフェースの1つですし、パソコンを繋ぐハードのコネクタもインターフェースです。その中で、Javaのインターフェースという場合は、プログラミングにおけるソフトウェア・インターフェースのことです。
プログラミングの分野では、「抽象型(=インターフェース)」と呼ばれています。インターフェースにはクラスのメソッド(メソッド名や型など)を置いてルールだけを決めておけます。そのため、実装は他のクラスに任せて、約束となる機能の設計図だけを用意する仕組みです。
APIとの違い
他の機能をクラスで使うという点では、インターフェースとAPIは分類上、同じものとなります。なぜなら、APIは「Application Programming Interface」で、概念の中にインターフェースが含まれているからです。
APIは外部からライブラリなどのクラスをまとめてインターフェースとして使えるようにします。つまり、外部から呼び出して機能を共有して使うのがAPIです。
一方、内部に用意してプログラムを動かすのがインターフェースです。もちろん、Javaには標準APIと外部APIがあり、実行時にAPIが呼ばれて使われています。インターフェースを実装するだけではAPIと呼ばないこともあるため、上記のような簡単な区別をつけるのです。
2.Javaでインターフェースを使うメリット
Javaの開発では、インターフェースを使うことで得られるメリットがあります。以下に、3つのメリットを取り上げます。
メソッドの呼び出し方が統一される
1つ目のメリットは、それぞれのクラスにあわせてメソッドの呼び出しを変える必要がないことです。Javaでは、メソッドを複数用意して、それを呼び出す場合に、呼び出すメソッドや戻り値に合わせた呼び出し方と実装の変更が必要となります。
例えば、インターフェースが仮に使えないとすると、以下のようなメソッドの呼び出し方の例がその1つです。
class JapaneseMessage { public String getJapaneseMessage() { return "こんにちは"; } }
public String getEnglishMessage() { return "Hello"; } }
public static void main(String[] args) { JapaneseMessage jm = new JapaneseMessage(); EnglishMessage em = new EnglishMessage();
System.out.println(em.getEnglishMessage()); } } |
出力の際に、「jm.getJapaneseMessage()」と「em.getEnglishMessage()」で異なるメソッドを呼び出しているのがわかります。
しかし、インターフェースを使えば、以下のように「.getMessage()」で呼び出しを統一できます。
interface MessageProvider { String getMessage(); }
public String getMessage() { return "こんにちは"; } }
public String getMessage() { return "Hello"; } }
public static void main(String[] args) { MessageProvider mp1 = new JapaneseMessage(); MessageProvider mp2 = new EnglishMessage();
System.out.println(mp2.getMessage()); } } |
この例では日本語と英語だけです。そこにフランス語やドイツ語、韓国語などを追加して呼び出し方を増やしても、その書き方は上記のように同じ方法で対応可能です。
複数人の開発でもルールを決めておける
2つ目のメリットは、企業やチームで複数人の開発をする場合にルールを決めて工程を進められることです。
事前に、インターフェースに「1つだけの機能をもたせる」「使わない機能を実装しない」など決めておき、拡張性や再利用性に配慮した使い方可能となります。
1つ目のメリットとも関連しており、呼び出し方も一定です。企業やチーム開発では、どうしても開発のコードを異なる人物や組織が作って、それにより差が生じ、実装方法がバラバラになります。
その結果、拡張のための機能を追加したくても、コードを別々の設計仕様に対応させることが難しくなるのです。
インターフェースならその点、拡張性を確保して追加できるほか、保守やデバッグの際にも混乱が少ないでしょう。それに想定外のバグも減らせるため、コードの堅牢性が高まります。
多重継承の代理手段になる
3つ目のメリットは、インターフェースが継承の代わりになって、多重継承の禁止ルールを回避できることです。Javaでは多重継承が使えない仕様となっており、複数のクラスから1つのクラスに継承して、別々の機能を追加することができません。
インターフェースなら機能を1つずつ実装して、それら複数をまとめて1つのクラスでimplementsして実装可能です。
3.Javaでインターフェースの基本となる使い方
ここでは、インターフェースの基本的な使い方として実際のコード作成の手順を解説します。
インターフェースの宣言の仕方
Javaでは、インターフェースとクラスの実装、実行側のクラス実装でそれぞれ3つのコードを組み立てるのが作成の基本です。その際に、最初に書くのが「インターフェースの宣言」の部分です。
インターフェースの宣言を構文で示すと以下のとおりです。
public interface インターフェース名 { 戻り値の型 メソッド名(); } |
この構文の形で宣言を行うと下記のようになります。
public interface Hello { void helloSample(); } |
publicは必須ではなく、付けておくとパッケージ外からのアクセスが可能となります。
「package-private」(public省略)で使用の場合は不要です。
値を返す場合は、以下のように文字列型や数値のプリミティブ型を指定することも可能です。
public interface Message { String getMessage(); } |
public interface Calculator { int add(int a, int b); } |
ただし、上記の「interface Calculator」の場合、引数にa,bの変数を書いていますが、「int add(1, 2)」と実際の数字を書くのはNGです。あくまでも定義上は設計図として使うため、引数には変数名のみを使用します。
主な戻り値の型に使われるのは、上記の「void(返り値なし)」や「String(文字列)」、「int(整数)」、「boolean(真偽値)」などが代表的です。
また、定数の定義もできます。ただし、メソッド定義の前にオプション的な扱いで追加することが通例です。その場合、「int num_max = 100;」のように書くことができます。
引数とは違って、定数ですから具体的な数字を書くことが許されています。その際、修飾子には「public static final」しか取れず、システム上は自動的にこの修飾子が追加されることです。したがって、定数を書くときは修飾子を開発者が省略してintから書くことも珍しくありません。
インターフェースの実装の仕方
宣言のあとに書くのがインターフェース実装です。以下は実装部分の構文です。
public class クラス名 implements インターフェース名 { @Override 戻り値の型 メソッド名(引数) { //処理内容 } } |
インターフェースの実装段階では、メソッドを再定義することで利用します。このとき、「@Override」を省略することがありますが、Javaでは省略しない書き方がおすすめです。
また、数字を使った引数は、この実装段階でも禁止となるルールです。そのため、変数で書くことが必要となります。実装では、変数を使ったメソッドの具体的な処理を記述することが目的です。
public class HelloImpl implements Hello { @Override public void helloSample() { System.out.println("Helloインターフェースの具体的な実装"); } } |
public class MessageImpl implements Message { @Override public String getMessage() { return "getMessageメソッドの返り値・文字列型による実装"; } } |
public class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } } |
実装部分は、独立して書くのではなく、先程のインターフェースの宣言の後に一緒に書きます。
インターフェース実装を呼び出して実行
インターフェースの実装をした後は、実行側でまとめて呼び出すことができます。以下は、インターフェースを実装したメソッドをまとめて呼び出して使うコード例です。
public class Main { public static void main(String[] args) {
hello.helloSample();
System.out.println(message.getMessage());
int sum = calculator.add(3, 7); System.out.println("3 + 7 = " + sum); } } |
基本的にはクラスでメソッドを実行するときのインスタンスの作成後に、オブジェクトを格納した変数を指定した参照「hello.helloSample()」や「message.getMessage()」、「calculator.add(3, 7)」の3つそれぞれでメソッドを呼び出しています。
ただし、上記のコードは呼び出すインターフェースの宣言と実装部分があって初めて実行できるクラスです。
4.ベストプラクティスで押さえるべきポイント
インターフェースでベストプラクティスを実現するためには、コード作成時に以下のポイントを押さえることが大事です。
インターフェース名はメソッド動作の(形容的な)名称をつける
処理内容は後で決められる設計を基本とする
staticメソッドは補助的な役割に使用先を限る
defaultメソッドを多用しない
継承よりもインターフェースを優先する
コード全体でインターフェースの継承を増やしすぎないことに注意する
ジェネリクスには型よりインターフェースを指定する
状況に応じて「依存性注入(DI)」を使う
例えば、インターフェース名には動作や形容的な名前をつけて、使う人がわかりやすくします。また、インターフェースの強みである「処理は後から決められる」部分を活かすために、具体的な設定は別クラスに任せることです。特に、staticメソッドやdefaultメソッドは場面を限定するのがおすすめです。
それからプログラムに継承が必要なケースでは、インターフェースの利用を優先し、インターフェース自体を継承する場合は複雑にならないようにし、数を増やしすぎないことに気をつけましょう。
特にジェネリクスでは、型よりもインターフェースを指定することで柔軟な設計ができます。
関連記事
【2025年版】Javaの最新バージョン|確認方法やインストール方法を解説
Java標準ライブラリ&外部ライブラリ一覧ガイド|選び方・使い方・作成方法など解説
5.Javaのインターフェースを応用した書き方
ここからは、基本の使い方をさらに発展させた実践的なコードの書き方を解説します。
defaultメソッドの使用
まずは、インターフェースでJava8から登場した新たな「defaultメソッド」の書き方があります。defaultメソッドとは、「default」というアクセス修飾子を付けたメソッドのことです。インターフェースでは原則できなかった具体的な設計ができます。
public interface Greeting { default void sayHello() { System.out.println("こんにちは"); } } |
public class HelloUser implements Greeting { } |
public class Main { public static void main(String[] args) {
user.sayHello(); } } |
この例では、interfaceの宣言にdefault修飾子を使って、具体的な処理内容が書かれています。もちろん、インターフェースを用意する意味が薄れないようにする必要があります。
主な使い道としては、旧クラスに新たなメソッドを加えたいケース、クラスを変更せずに対応したいケースなどです。
その際、すべてのクラスを書き換えなくても変更に対応できます。つまり、修正の手間が減ることになるのです。
また、共通する動作だけは決めておきたいときにも使えます。しかし、インターフェースの宣言の原則は、設計図のみを提供して、実装部分で具体的な処理を書くことですから、宣言にも処理方法を書くときの使い道は絞るべきです。
ラムダ式を組み合わせる
インターフェースの中でも、1つのメソッドだけを持つのが「関数型インターフェース」です。これを実装する際に、使えるのが「ラムダ式」です。
ラムダ式では、コード記述の量を減らせるため、開発でのコーディング作業の負担を少なくできます。以下はラムダ式で書いた例です。
@FunctionalInterface public interface MessagePrinter { void print(String message); } |
public class LambdaTest { public static void main(String[] args) { MessagePrinter printer = (text) -> System.out.println("メッセージ: " + text);
} } |
上記の通り、ラムダ式では「->」で繋いで処理内容を簡単に書くため、通常のメソッド記述よりも簡潔な書き方です。全体的にシンプルでコード量の少ない設計となります。
インターフェースの継承
インターフェースは、クラス同様に継承して使うことができます。継承を使うことで機能を追加し、さまざまな設計ルールを加えることも可能です。
public interface Greeting { void sayHello(); } |
public interface MorningGreeting extends Greeting { void sayGoodMorning(); } |
public class GreetingImpl implements MorningGreeting { @Override public void sayHello() { System.out.println("こんにちは"); } |
@Override public void sayGoodMorning() { System.out.println("おはようございます"); } |
public static void main(String[] args) { GreetingImpl greet = new GreetingImpl(); greet.sayHello(); greet.sayGoodMorning(); } } |
上記の例では、挨拶の基本の仕方をextendsで継承して拡張し、implementsで2つのメソッドを実装、最後に実行での呼び出しをしています。
そのため、別のインターフェースとして追加する必要がなく、似たインターフェースを集めて整理することにも役立つのです。
6.Javaのインターフェースの実践な使い方
基本と応用を踏まえて、実践的な使い方ができる3つの方法を紹介します。
多態性(ポリモーフィズム)の機能を使用する
まずは、Javaの特徴である「多態性(ポリモーフィズム)」の機能を実装する使い方です。別々のオブジェクトを同じ方法で追加して使用することができます。
public interface Printer { void print(); } |
public class TextPrinter implements Printer { @Override public void print() { System.out.println("文章を印刷します。"); } } |
public class ImagePrinter implements Printer { @Override public void print() { System.out.println("画像を印刷します。"); } } |
public class PrintApp { public static void main(String[] args) { Printer printer;
printer.print();
printer.print(); } } |
上記の例では、Printerインターフェースを使って、先に設計図を示し、実装クラスのメソッドを使えます。実行クラスに大きな変更を加える必要がないため、多態性を上手く活用している事例です。
イベント処理に使う
次に、イベントリスナーなどのイベント処理に使う方法です。イベントリスナーとは、指定のイベントが発生したときに、反応して処理することです。この処理は基本的にインターフェースを使って以下のように設計と実装が行われます。
public interface ClickListener { void onClick(); } |
public class Button { private ClickListener listener;
this.listener = listener; }
if (listener != null) { listener.onClick(); } } } |
public class EventApp { public static void main(String[] args) { Button button = new Button(); button.setClickListener(new ClickListener() { public void onClick() { System.out.println("ボタンがクリックされました。"); } });
} } |
上記の例では、「ボタンがクリックされました。」という出力にもあるように、ボタンの実行処理をイベントリスナーとしてインターフェースで実装しています。
この方法では、同じボタンを使って、別の処理を後で追加するなど、変更しやすくなるなど柔軟に対応できるメリットがあるのです。
実際のボタン実装はGUIを含むためもう少し複雑ですが、インターフェースで処理を変えられる強みが上記からも十分に伝わるでしょう。
ストラテジーパターンの切り替え処理に使う
インターフェースに活用しやすい実践向けのデザインパターンにストラテジーパターンを利用することがあります。ストラテジーパターンとは、異なる戦略を切り替える方法です。
大まかに分類すると多態性(ポリモーフィズム)に位置付けられます。しかし、ストラテジーパターンはより詳細なデザインパターンとして、戦略を切り替えることに主眼があります。
public interface PrintStrategy { void print(String text); } |
public class UpperCasePrinter implements PrintStrategy { @Override public void print(String text) { System.out.println(text.toUpperCase()); } } |
public class LowerCasePrinter implements PrintStrategy { @Override public void print(String text) { System.out.println(text.toLowerCase()); } } |
public class Printer { private PrintStrategy strategy;
this.strategy = strategy; }
strategy.print(text); }
Printer printer = new Printer();
printer.printText("Strategy Pattern Example");
printer.printText("Strategy Pattern Example"); } } |
上記の例では、if文を使わずにストラテジーパターンで「大文字」と「小文字」のプリント(出力)を戦略で切り替えて使えるコードです。シンプルなのでコードが見やすく、追加にも対応できる拡張性もあります。
関連記事
Java GUIプログラミング入門|Swing・JavaFX比較やEclipse画面作成、デザインなど解説
7.Javaのインターフェースのよくある質問
ここでは、Javaのインターフェースを実装する際によくある質問を取り上げます。
抽象クラスとの違いは?
具体的な処理を書かないという点は、Javaで使えるインターフェースと「抽象クラス」は似ています。しかし、インターフェースと抽象クラスには違いとして下記の点が挙げられます。
内部の処理を普通に書くことができない(Java 8からはdefaultキーワードで書ける)
コンストラクタを内部に持つことができない
「public static final」以外、他のフィールドを定義できない
(インターフェース間の)多重継承が可能であり、また(クラスによる)複数実装の対象となる
以上はインターフェースが、抽象クラスと比べたときに違う点です。特に押さえておきたい点は、インターフェースではコンストラクタだけはどうやっても定義できないことです。
フィールドも「public static final」以外に、別途定義することが難しいなど細かな部分に明確な違いがあります。似ているからと代わりに使うことはできないため注意が必要です。
Javaではインターフェースのインスタンス化はできない?
Javaでは、インターフェースのルールの1つとして「インスタンス化できない」ことが決まっています。しかし、インターフェースのコードを見ると、呼び出し側でnewしていることがあり、これがインターフェースのインスタンス化に見えて、初心者を混乱・勘違いさせる原因です。
呼び出し側でnewしてインスタンスを使っているのは、実装クラスの代入に過ぎません。変数にインスタンスを入れて、クラス名を増やすことで、もとのインターフェースとクラスの依存性を薄めています。柔軟な設計をするためのテクニックとして、インスタンスを入れているわけです。
また、無名クラスでの使用もnewする理由の1つです。1回だけ使う場合は、implementsするクラスを省略して、無名クラスとしてインスタンスを呼び出すことができます。この場合も、インターフェースのインスタンス化ではありません。
関連記事
JavaとPythonの違いとは?文法比較などエンジニア初心者から現役開発者向けに解説
JavaとJavascriptの違いは?文法の特徴から関係性・学習方法まで徹底解説!
8.まとめ
今回は、Javaにおけるインターフェースの特徴やメリット、基本から応用、実践の使い方までを解説しました。インターフェースは通常のクラスでメソッドを書くよりも約束事を決めておけます。また、多重継承の代わりとなるのもメリットです。
インターフェースの基本となる使い方を押さえておけば、応用や実践活用でも書き方を変えずに容易に実装することが可能となります。上記を参考にして、さまざまな場面でインターフェースを使ったコード作成を試しましょう。
本記事が皆様にとって少しでもお役に立てますと幸いです。
「フリーランスボード」は、数多くのフリーランスエージェントが掲載するITフリーランスエンジニア・ITフリーランス向けの案件・求人を一括検索できるサイトです。
開発環境、職種、単価、稼働形態、稼働日数など様々な条件から、あなたに最適なフリーランス案件・求人を簡単に見つけることができます。
単価アップを目指す方や、自分の得意なスキルを活かせる案件に参画したい方は、ぜひ「フリーランスボード」をご利用ください。
自身に最適なフリーランスエージェントを探したい方はこちらよりご確認いただけます。