インタフェースはインスタンスを作らない!
前ページでは、インタフェースとクラスは別、ということを説明しました。
では、なぜこのように、クラスとインタフェースは分けられているのでしょう。
それは、インタフェースがインスタンスを作らないからです。
クラスは、インスタンスを作るための「設計図」です。newでインスタンスを作るための情報、それがクラスです。
ところが、インタフェースにはその機能がありません。
インタフェースはインスタンスを作りません。
まず、インタフェース単体では当然インスタンスを作れません。
また、インタフェースの実装クラスを元にインスタンスを作っても、その中に「インタフェースから作られたインスタンス」は含まれていません。
それを、プログラムを通して見てみましょう。
/**
* インタフェース。
*/
interface HasToStringInterface
{
/**
* 抽象メソッド。
* 何か分からないけど文字列を返します。
*/
String toString();
}
/**
* Objectクラスのサブクラスで、かつ
* HasToStringInterfaceインタフェースの実装クラス。
*/
class InheritedFromObject implements HasToStringInterface
{
// 中身ナシ!
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class InheritedFromObjectRunner
{
public static void main( String[] args )
{
// InheritedFromObjectクラスのインスタンスを1つ作ります。
// 参照型変数の型はHasToStringInterfaceインタフェースです。
HasToStringInterface ref = new InheritedFromObject();
// toString()メソッドを呼び出します。
String s = ref.toString();
System.out.println( s );
// 実行結果:
// InheritedFromObject@1c6f579
// (値は毎回変わります)
}
}
// InheritedFromObjectRunner.java /** * インタフェース。 */ interface HasToStringInterface { /** * 抽象メソッド。 * 何か分からないけど文字列を返します。 */ String toString(); } /** * Objectクラスのサブクラスで、かつ * HasToStringInterfaceインタフェースの実装クラス。 */ class InheritedFromObject implements HasToStringInterface { // 中身ナシ! } /** * 実行用クラス。このクラスを実行してください。 */ class InheritedFromObjectRunner { public static void main( String[] args ) { // InheritedFromObjectクラスのインスタンスを1つ作ります。 // 参照型変数の型はHasToStringInterfaceインタフェースです。 HasToStringInterface ref = new InheritedFromObject(); // toString()メソッドを呼び出します。 String s = ref.toString(); System.out.println( s ); // 実行結果: // InheritedFromObject@1c6f579 // (値は毎回変わります) } }
まずHasToStringInterfaceインタフェースを用意します。
このインタフェースは抽象メソッドをひとつ持っています。
* インタフェース。
*/
interface HasToStringInterface
{
/**
* 抽象メソッド。
* 何か分からないけど文字列を返します。
*/
String toString();
}
/** * インタフェース。 */ interface HasToStringInterface { /** * 抽象メソッド。 * 何か分からないけど文字列を返します。 */ String toString(); }
次に、このインタフェースを実装したInheritedFromObjectクラスを作ります。
* Objectクラスのサブクラスで、かつ
* HasToStringInterfaceインタフェースの実装クラス。
*/
class InheritedFromObject implements HasToStringInterface
{
// 中身ナシ!
}
/** * Objectクラスのサブクラスで、かつ * HasToStringInterfaceインタフェースの実装クラス。 */ class InheritedFromObject implements HasToStringInterface { // 中身ナシ! }
このクラス、HasToStringInterfaceインタフェースを実装しておきながら、toString()メソッドを実装していません。
でもこのクラスはこれでちゃんと使えます。
なぜかというと、InheritedFromObjectクラスは自動的にObjectクラスから継承していて、そのObjectクラスがtoString()メソッドを持っているからです。
「8.2 Objectクラスってなに?」で説明したとおり、すべてのクラスはObjectクラスのサブクラスになるため、InheritedFromObjectクラスも自動的にObjectクラスのサブクラスになります。
そして、そのObjectクラスは「public String toString()」メソッドを持っています。これが、HasToStringInterfaceインタフェースのtoString()メソッドの実装になります。
これまでは、抽象メソッドの実装は、実装先のクラスで作っていました。でも実は、スーパークラスに抽象メソッドと同じ名前・引数のメソッドがあればそれで実装できちゃうんです。
では、InheritedFromObjectクラスのインスタンスを作ってみましょう。
// 参照型変数の型はHasToStringInterfaceインタフェースです。
HasToStringInterface ref = new InheritedFromObject();
// InheritedFromObjectクラスのインスタンスを1つ作ります。 // 参照型変数の型はHasToStringInterfaceインタフェースです。 HasToStringInterface ref = new InheritedFromObject();
InheritedFromObjectクラスのインスタンスが作られると、そのインスタンスの中に、Objectクラスのインスタンスと、HasToStringInterfaceインタフェースが含まれます。
この時、HasToStringInterfaceインタフェースのtoString()メソッドは、ObjectクラスのtoString()メソッドにひもを付けます。
この時、インタフェースはインスタンスを作りません。あくまで「インタフェース」としてインスタンスの中に入っています。
インタフェースはフィールドもメソッドも持ちません。そのため、「インスタンスを作ってその中にフィールドやメソッドを入れる」ということをする必要がありません。つまりインタフェースは入れる物がないんです。普通のクラスが持つフィールドやメソッドといった「インスタンスの中に入れる物」が、インタフェースにはないんです。
インタフェースは抽象メソッドを持っています。この抽象メソッドは中身がありません。では何のためにあるかというと、単に実装メソッドにひもを付けているだけです。ひもそのものと言ってもいいかもしれません。
つまり、インタフェースはメソッドにひもを付けるためのものなのです。
そのため、新しくメソッドを作らなくても、このようにスーパークラスのメソッドにひもを付けることができ、インタフェースを通してそれを呼び出すことができるわけです。
String s = ref.toString();
System.out.println( s );
// 実行結果:
// InheritedFromObject@1c6f579
// (値は毎回変わります)
// toString()メソッドを呼び出します。 String s = ref.toString(); System.out.println( s ); // 実行結果: // InheritedFromObject@1c6f579 // (値は毎回変わります)
インタフェースの参照型変数の抽象メソッドを呼び出すと、ひもが付いている実装メソッドが呼び出されます。
インタフェースを実装したことで、そのひもを通して操作ができるようになったわけです。
このように、インタフェースは単なるひも。実装したメソッドを呼び出すためだけのものです。
インタフェースは「中身」がなく、そのためインスタンスを作る必要がありません。なのでインスタンスを持たず、ただひもだけ付けるわけです。
中身がないからいっぱい付けれる!
「6.3 2つのクラスをくっつけられる?」で、クラスは「2つのクラスから1つのクラスを作れない」ということを説明しました。
インタフェースにその制約がないのは、この「中身がない」という特徴が理由です。
多重継承したクラスは、インスタンスの中にいろんなインスタンスがごちゃ混ぜに入って、その管理が難しくなります。
でもインタフェースはいくら実装しても問題ありません。だってそのインスタンスがないんですから。
つまり、いくらインタフェースを実装しても、「フィールド重複してるんじゃね?」「どのフィールドを使う?」「どのメソッドが呼び出される?」といった問題がインスタンスを作らないため起きません。
インタフェースを実装すると、ただひもが付けられるだけ。何も増えません。抽象メソッドは実装メソッドと見た目(名前・引数)が同じなので、いくら抽象メソッドが増えてひもが増えても、実質なにも増えたことにはならないのです。なので、いくら実装しても大丈夫、というわけです。
ちなみに、インタフェースの抽象メソッドが自動的にpublicになるのは、これが理由です。
インタフェースの抽象メソッドは「外から呼び出せるようひもを付ける」ためのものです。外から呼び出す、という目的のため自動的にpublicが付いているわけです。
中身がなく、ただ外から呼び出すためだけの機能、それがインタフェースというわけです。