interface で契約を定義
interface で契約を定義 とは
Java の interface を使って「契約」だけを定義し、implements で複数のクラスに同じ振る舞いを約束させる方法を学ぼう。
interface とは何か ─ 「契約」を書く仕組み
ここまでのレッスンで、class を使って「データ + 振る舞い」を一緒に包む方法を学んできました。Bank クラスや Product クラスを作って、フィールドとメソッドを並べる、というやり方です。しかし、現場の Java を眺めていると class と並んでもう一つよく登場するキーワードがあります。それが interface です。
interface は、ひとことで言えば 「メソッドのシグネチャ (=名前と引数と戻り値) だけを並べた契約書」 です。中身の実装は書かず、「このクラスを名乗るなら、こういうメソッドを必ず持っていてね」という約束ごとだけを定義します。Java で Android アプリを作る場面でも、Spring で Web API を組む場面でも、Comparable Runnable List Map など、いたるところに interface が顔を出します。
Java
interface Runnable2 {
String name();
}このコードを読むと「name() というメソッドを持っているはず」とだけ書いてあり、return がありません。interface はそれだけで完結していて、{ の中身は 後から実装するクラスが書く ことになります。
interfaceの最大の価値は「中身を後回しにできる」ことです。先に「こういうものが欲しい」と契約だけ書いておけば、誰がどう実装しようと利用側のコードは変えなくて済みます。これは大規模開発でチーム分担をするときに、ものすごく効いてきます。
class と interface の違い
両者の違いを表にまとめると、見通しが良くなります。
| 観点 | class | interface |
|---|---|---|
| キーワード | class | interface |
| インスタンス化 | new できる | 直接 new できない |
| フィールド | 自由に持てる | public static final のみ |
| メソッドの中身 | 書く | 原則書かない (default は例外) |
| 多重○○ | クラスは 1 つしか extends できない | 複数 implements できる |
つまり class は「実体を作る型」、interface は「型の名札 / 契約」と覚えるのが分かりやすいです。Java には「クラスの多重継承は禁止」というルールがありますが、interface を複数 implements するのは自由なので、機能の組み合わせは interface で表現するのが王道になります。
implements で契約を実装する
interface を実際に使うには、それを 実装する クラスを書きます。書き方は class クラス名 implements インターフェース名 です。extends ではなく implements を使うのがポイントで、ここを間違えると即コンパイルエラーになります。
Java
interface Runnable2 {
String name();
}
class MyRunner implements Runnable2 {
@Override
public String name() {
return "MyRunner";
}
}上のコードでは、MyRunner クラスが Runnable2 という契約を引き受け、name() の中身を "MyRunner" を返すように書いています。@Override アノテーションは「これは親の契約を満たすメソッドですよ」というマークで、書いておくとタイポしたときにコンパイラが教えてくれるので強く推奨されます。
実装メソッドは必ず
publicで書く約束です。interface側でpublicと書かれていなくても、Javaは自動的にpublic abstractとして扱うため、実装側でprivateやprotectedに 下げる ことはできません。
多重実装 ─ interface の真骨頂
class は extends できる親クラスが 1 つだけですが、interface はコンマで区切ってカンマ区切りで いくつでも implements できます。これは現実世界の「役割」を表現するのにとても便利です。
Java
interface Runnable2 { String name(); }
interface Printable { void printInfo(); }
class MyTask implements Runnable2, Printable {
@Override
public String name() { return "MyTask"; }
@Override
public void printInfo() {
System.out.println("Task: " + name());
}
}MyTask は「実行できる」役割と「情報を表示できる」役割の両方を兼ね備えています。実装側で約束されたメソッドを すべて 書かないとコンパイルエラーになるので、抜け漏れの心配もありません。
UML 風に書くと、interface は破線で表現します。MyTask のように 2 本の破線が伸びていれば「両方の契約を引き受けている」ことが一目で分かります。
default メソッドと static メソッド ─ Java 8 以降の進化
古い interface は「中身を絶対に書かない純粋な契約書」でしたが、Java 8 以降では default キーワードを付けることで interface の中にメソッドの中身も書けるようになりました。これは既存の interface にメソッドを後から足したいとき、すでに実装している全クラスを壊さずに済むよう導入された仕組みです。
Java
interface Runnable2 {
String name();
default String greet() {
return "Hello from " + name();
}
static Runnable2 of(String n) {
return () -> n;
}
}default メソッドは、実装クラス側で オーバーライドしてもしなくても OK という緩い扱いです。static メソッドは interface 名から直接呼び出すユーティリティで、Runnable2.of("X") のように使います。Comparator.comparing(...) などはこの仕組みを使った代表例です。
defaultメソッドを乱用すると「interfaceなのかabstract classなのか分からない設計」になりがちです。あくまで「契約 + 共通実装の補助」として節度を持って使うのが現代の作法です。
よくある間違い
初学者が interface で踏むつまずきを 3 つ並べます。先回りして知っておきましょう。
extendsとimplementsの取り違え — クラスがクラスを継承するときはextends、クラスがinterfaceを実装するときはimplementsです。class MyRunner extends Runnable2と書くと「Runnable2クラスを継承しようとしている」と解釈され、Runnable2がinterfaceの場合はno interface expected here系のエラーになりますinterfaceのフィールドを変数のつもりで書く —interfaceの中にint count = 0;と書くと、Javaは黙ってそれをpublic static final int count = 0;と解釈します。インスタンスごとの状態は持てず、後から代入もできません。状態を持たせたいならinterfaceではなくabstract classを選びましょう- 実装メソッドを
public以外で書く — 契約上のメソッドは暗黙的にpublic abstract扱いなので、実装側をprivateやpackage-private(修飾子なし) にすると「アクセス修飾子を弱められない」エラーになります。@Override public String name()の形を体に染み込ませてください
少し意外なのが、最後の「interface 同士の継承は extends」というルールです。interface A extends B, C のように、interface は他の interface を 複数 extends できます。
やってみよう
それでは、今回の課題に挑戦してみましょう。やることは次のとおりです。
Runnable2というinterfaceを定義し、String name();メソッドを宣言するMyRunnerクラスでimplements Runnable2し、コンストラクタで受け取った文字列をname()から返すように実装するSolution.runnerName(String name)の中でnew MyRunner(name)をRunnable2型変数で受けてからname()の戻り値をreturnする
テストでは runnerName("MyRunner") だけでなく runnerName("Hello") や runnerName("") のように別の引数でも呼ばれます。name() のシグネチャがズレていたり、戻り値をハードコードしていたりするとここで fail するので、必ず引数を内部に渡してから Runnable2 型経由で取り出してください。interface を class と同じファイルに書いて構いません (public を付けない interface / class は同居できます)。
慣れてきたら、Printable のような 2 つ目の interface を作って多重 implements を試したり、default メソッドを足してみたりすると、interface の表現力が一段と分かってきます。次のレッスンでは、interface を引数や戻り値の型として使う ポリモーフィズム に踏み込んでいきます。
よくある質問
Q. abstract クラスと interface はどう使い分けますか?
A. 型の契約だけ定義するなら interface、共通実装も含めて部分的に書きたいなら abstract クラスです。Java 8 以降は interface も default メソッドで実装を持てるため、abstract の出番は減っていますが、フィールド共有が必要なら abstract が必要です。
Q. interface に変数を書けますか?
A. 暗黙的に public static final な定数だけ書けます。ミュータブルなフィールドは持てないため、状態を持たせたい場合は abstract クラスにしてください。定数置き場として使う場合も、専用クラス + private コンストラクタの方がベターな設計です。
Q. 1 つのクラスに複数の interface を実装できますか?
A. できます。implements A, B, C のようにカンマ区切りで列挙します。複数の default メソッドが同名で衝突した場合はコンパイルエラーになるため、override で明示的に解決します。多重継承の難しさをこの形で限定的に解放しているのが Java の設計です。
次のレッスン
次は default メソッド で、Java の interface を使って「契約」だけを定義し、implements で複数のクラスに同じ振る舞いを約束させる方法を学ぼう を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- interface の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. interface とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
interface Runnable2を定義し、String name();を宣言することMyRunnerクラスをimplements Runnable2で実装し、コンストラクタで受け取った文字列をname()から返すことrunnerName(String name)の中でMyRunnerを生成し、必ずRunnable2型変数で受けてからname()を呼ぶこと
入出力例
test-cases.txt
runnerName("MyRunner") → "MyRunner"
runnerName("Hello") → "Hello"
runnerName("") → ""