interface で契約を定義
親を 1 つ選んだ時点で、もう身動きが取れない
extends できる親は 1 つだけです。ある型に「保存できる」「表示できる」「送信できる」の 3 つの役割を持たせたくても、継承では 1 つしか選べません。しかも継承は親の実装まで丸ごと引き受けるので、役割を足したいだけの用途には重すぎます。
必要なのは中身ではなく、「このメソッドを持っている」という約束だけです。それを書くのが interface です。
中身を書かず、名前と型だけ並べる
Java
interface Sensor {
int read();
}read() に本体はありません。「Sensor を名乗るなら、int を返す read() を必ず持て」という契約だけがここにあります。引き受ける側は implements と書きます。
Java
class TempSensor implements Sensor {
@Override
public int read() {
return 23;
}
}契約のメソッドは暗黙に public なので、実装側も public で書きます。private に絞ることはできません。実装を書き忘れればコンパイルエラーになるので、抜け漏れは起きません。クラスを継承するときは extends、契約を引き受けるときは implements で、ここを取り違えるとその場でエラーになります。
implements はいくつでも並べられる
extends と違い、implements はカンマ区切りで何個でも書けます。冒頭の「3 つの役割」はこう表せます。
Java
interface Storable { void save(); }
interface Printable { void printInfo(); }
class TempSensor implements Sensor, Storable, Printable {
// 3 つぶんのメソッドをすべて実装する
}extends は 1 つ、implements は何個でも。これが両者の一番大きな違いです。実装を分けてもらうのが継承、名札を増やすのが契約、と考えると使い分けに迷いません。
| 観点 | abstract class | interface |
|---|---|---|
| いくつ持てるか | 親は 1 つだけ | いくつでも |
| フィールド | 普通に持てる | 定数だけ |
| 向いている表現 | 「〜の一種である」と共通実装 | 「〜できる」という役割 |
Comparable Runnable AutoCloseable といった標準の型がどれも interface なのは、いずれも「〜できる」という役割だからです。
やってみよう
runnerName(String name) を完成させてください。
interface Runnable2を定義し、String name();を宣言するMyRunnerクラスをimplements Runnable2で作る。コンストラクタで受け取った文字列を、name()から返せるようにするrunnerNameの中でMyRunnerを生成し、Runnable2型の変数で受けてからname()を呼ぶ
手順 3 で MyRunner 型ではなく契約の型で受けるのが要点です。呼ぶ側が具体的なクラス名を知らなくても、契約さえ満たしていれば動く、という形をここで作っておきます。空文字を渡したときは、空文字がそのまま返ってきます。
要件
interface Runnable2を定義し、String name();を宣言することMyRunnerクラスをimplements Runnable2で実装し、コンストラクタで受け取った文字列をname()から返すことrunnerName(String name)の中でMyRunnerを生成し、必ずRunnable2型変数で受けてからname()を呼ぶこと
入出力例
runnerName("MyRunner") → "MyRunner"
runnerName("Hello") → "Hello"
runnerName("") → ""