目次 | |

9. インタフェース

インタフェース宣言は,メンバを定数及び抽象メソッドとする新しい参照型を導入する。この型は実装をもたないが,関係ないクラスがその抽象メソッドに実装を提供することで,そのインタフェースを実装できる。

Javaプログラムは,関係しているクラスが共通の抽象スーパクラスを共有すること又はメソッドをObjectに追加することを不要とするために,インタフェースを使用することができる。

インタフェースは,複数の他のインタフェースの直接的拡張(direct extension)と宣言してよい。これによって,隠ぺいされるかもしれない定数以外の,インタフェースが拡張した元のインタフェース複数の他の抽象メソッド及び定数を,暗黙的に規定する。

クラスは,複数のインタフェースを直接的に実装する(directly implement)と宣言してもよい。これによって,そのクラスの任意のインスタンスはインタフェースによって規定されるすべての抽象メソッドを実装する。クラスは,必然的に,その直接的スーパクラス及び直接的スーパインタフェースのすべてのインタフェースを実装する。この(多重)インタフェース継承は,オブジェクトがいかなる実装も共有せずに(多重の)共通的な振舞いを実装することを可能とする。

宣言型をインタフェース型とする変数は,その値として,指定されたインタフェースを実装すると宣言されるクラスのインスタンスであるオブジェクト参照をもっていてよい。クラスがインタフェースのすべての抽象メソッドをたまたま実装しているのでは十分でない。そのクラス又はそのスーパクラスの一つは,インタフェースを実装するものと実際に宣言されていなければならない。そうでないときには,クラスはそのインタフェースを実装するとは考えない。

9.1 インタフェース宣言

インタフェース宣言は,新しい参照型を規定する。

インタフェースを名前付けするIdentifierが,同じパッケージ内の他のクラス又はインタフェースの名前として出現すれば,コンパイル時エラーが発生する。インタフェースを名前付けするIdentifierが,そのインタフェース宣言を含むコンパイル単位における単一型インポート宣言(7.5.1)経由で知られるクラス又はインタフェースの名前として出現すれば,コンパイル時エラーが発生する。次に例を示す。

class Point { int x, y; }

interface Point { void move(int dx, int dy); }
同じパッケージ内のclass及びinterfaceは,同じ名前をもてないので,コンパイル時エラーが発生する。

9.1.1 インタフェース型名の有効範囲

Identifierはインタフェースの名前を規定し,その有効範囲はインタフェースが宣言されるパッケージ全体とする。これは,クラス型名と同じ有効範囲規則とする。クラスを含む例は8.1.1を参照のこと。

9.1.2 インタフェース修飾子

インタフェース修飾子(interface modifier)が,インタフェース宣言に先行することがある。

アクセス修飾子publicは,6.6で記述する。同じ修飾子が一つのインタフェース宣言で2回以上出現するとき,コンパイル時エラーが発生する。

9.1.2.1 抽象インタフェース

すべてのインタフェースは,暗黙的にabstractとする。この修飾子abstractは,もはやインタフェースに対しては意味がなく,新しいJavaプログラムでは使用しないことが望ましい。

9.1.3 スーパインタフェース

extends節が提供されれば,宣言されるインタフェースは、他の名前付けされた各インタフェースを拡張し,したがって,他の名前付けされたインタフェースのメソッド及び定数を継承する。これらの名前付けされたインタフェースは,宣言されるインタフェースの直接的スーパインタフェース(direct superinterface)とする。宣言されたインタフェースを実装するクラスは,このインタフェースを拡張し,実装となるクラスにアクセス可能なすべてのインタフェースも実装する。

次は,明確化のために4.3から再度記述する。

インタフェース宣言のextends節内の各InterfaceTypeは,アクセス可能なインタフェース型を名前付けしなければならない。そうでないときには,コンパイル時エラーが発生する。

インタフェースが直接的又は間接的にそれ自体を拡張する循環が存在すれば,コンパイル時エラーが発生する。

インタフェースに対しては,Objectに類似するものは存在しない。すなわち,すべてのクラスはクラスObjectの拡張だが,すべてのインタフェースが拡張となる単一のインタフェースは存在しない。

スーパインタフェース(superinterface)関係は,直接的スーパインタフェース関係の推移的閉包(transitive closure)とする。次のいづれかが真ならば,インタフェースKはインタフェースIのスーパインタフェースとする。

KIのスーパインタフェースであるときはいつでも,インタフェースIがインタフェースKサブインタフェース(subinterface)と言う。

9.1.4 インタフェース本体及びメンバ宣言

インタフェース本体は,インタフェースのメンバを宣言してよい。

インタフェース型で宣言されるメンバの名前の有効範囲は,インタフェース型宣言の本体全体とする。

9.1.5 インタフェースメンバ名へのアクセス

すべてのインタフェースメンバは,暗黙的にpublicとする。これらは,インタフェースもpublicと宣言されている,及びそのインタフェースを含むパッケージが7.1で記述されるとおりにアクセス可能ならば,そのインタフェースが宣言されているパッケージ外でアクセス可能とする。

9.2 インタフェースメンバ

インタフェースのメンバは,直接的スーパインタフェースから継承されるメンバ及びインタフェース内で宣言されるメンバとする。

インタフェースは,そのインタフェースが拡張するインタフェースから,そのインタフェースが隠ぺいするフィールド及び上書きするメソッドを除いたすべてのメンバを継承する。

9.3 フィールド(定数)宣言

インタフェース本体でのすべてのフィールド宣言は,暗黙的にpublicstatic及びfinalとする。書式としてこれらのフィールド修飾子のどれか又はすべてを,冗長だが指定してもよい。ただし,記述しないことが望ましい。

インタフェースにおける定数宣言は,修飾子synchronizedtransient又はvolatileのいずれも含んではならない。そうでないときには,コンパイル時エラーを引き起こす。

インタフェースが同じ名前の一つ以上のフィールド(8.3.3.3)を継承するのは可能とする。この状況は,それ自身ではコンパイル時エラーを引き起こさない。しかし,インタフェース本体内で単純名によっていずれかのフィールド参照しようとすると,参照があいまいなため,コンパイル時エラーが発生する。

同じフィールド宣言が,一つのインタフェースから複数の経路で継承されるかもしれない。この状況では,フィールドは1回だけ継承されると考えられ,単純名によってあいまい性なしに参照される。

9.3.1 インタフェース内でのフィールドの初期化

インタフェース本体内のすべてのフィールドは,初期化式をもたなければならない。これは定数式とする必要はない。インタフェースが初期化されるとき(12.4),変数初期化式が評価され,1回だけ代入が実行される。

インタフェースフィールドのための初期化式が単純名によって同じフィールド,又は同じインタフェース内で,ソーステキスト上,後方に宣言されている他のフィールドへの参照を含めば,コンパイル時エラーが発生する。そこで,次の式は,二つのコンパイル時エラーを引き起こす。


interface Test {
    float f = j;
    int j = 1;
    int k = k+1;
}
これは,jが宣言される前にfの初期化で参照されること,及びkの初期化がkそのものを参照していることに基づく。

(ここで微妙なことは,コンパイル時に定数値で初期化されたフィールドが,実行時には最初に初期化される,ということである。これは,クラスのstatic finalフィールド(8.3.2.1)にも適用される。特にこのことは,たとえ悪意のあるプログラムによっても,これらのフィールドが決してデフォルト初期値(4.5.4)をもたないことを意味する。詳細に関しては,12.4.2及び13.4.8を参照のこと。)

キーワードthis15.7.2)又はキーワードsuper15.10.215.11)がインタフェースのフィールドについての初期化式で出現すれば,コンパイル時エラーが発生する。

9.3.2 フィールド宣言の例

次にフィールド宣言に関するいくつかの(微妙な)点に関する例を示す。

9.3.2.1 あいまいな継承フィールド

例えば直接的スーパインタフェース内の二つが同じ名前をもつフィールドを宣言しているために同じ名前をもつ二つのフィールドが一つのインタフェースによって継承されれば,一つのあいまいなメンバ(ambiguous members)のいかなる使用も,コンパイル時エラーを生ずる。次に例を示す。


interface BaseColors {
    int RED = 1, GREEN = 2, BLUE = 4;
}

interface RainbowColors extends BaseColors { int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7; }
interface PrintColors extends BaseColors { int YELLOW = 8, CYAN = 16, MAGENTA = 32; }
interface LotsOfColors extends RainbowColors, PrintColors { int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90; }
インタフェースLotsOfColorsは,YELLOWと名前づけられた二つのフィールドを継承する。インタフェースが単純名によってフィールドYELLOWへの参照を含まない限り,これは問題ない。(そのような参照は,フィールドに対する変数初期化子内で発生し得る。)

たとえインタフェースPrintColorsが値8ではなくて値3YELLOWに与えても,インタフェースLotsOfColors内のフィールドYELLOWへの参照は,やはりあいまいさをもつ。

9.3.2.2 多重継承フィールド。

例えばインタフェース及びインタフェースの直接的スーパインタフェースの一つの両方が同じフィールドを宣言しているために,インタフェースから複数回同一のフィールドを継承していれば,一つのメンバだけが生ずるものとする。この状況は,そのままではコンパイル時エラーを引き起こさない。

前節の例では,フィールドREDGREEN及びBLUEがインタフェースLotsOfColorsで,インタフェースRainbowColors及びインタフェースPrintColorsを経由して複数回継承される。しかし,インタフェースLotsOfColors内でのフィールドREDへの参照は,フィールドREDの実際の宣言が1回だけなので,あいまいとは考えられない。

9.4 抽象メソッド宣言

アクセス修飾子public6.6で記述する。同じ修飾子が抽象メソッド宣言内に複数回現れれば,コンパイル時エラーが発生する。

インタフェース本体内でのすべてのメソッド宣言は,暗黙的にabstractとする。そこで,その本体は常にセミコロンで表現され,ブロックではない。Javaの旧バージョンとの互換性のために,書式上インタフェース内で宣言されたメソッドに対して冗長に,修飾子abstractを指定してもよい。ただし,記述しない方が望ましい。

インタフェース本体内でのすべてのメソッド宣言は,暗黙的にpublicとする。書式上インタフェースメソッドに対して冗長に修飾子publicを指定してもよい。ただし,記述しない方が望ましい。

インタフェースで宣言されたメソッドをstatic宣言してはならないことに注意すること。そうでないときには,コンパイル時エラーが発生する。これは,Javaにおいてstaticなメソッドがabstractであるはずがないからである。

インタフェースで宣言されたメソッドをnative宣言又はsynchronized宣言してはならないことに注意すること。そうでないときには,コンパイル時エラーが発生する。これは,それらのキーワードはインタフェースの属性ではなくて実装の属性を記述するからである。しかし,インタフェースで宣言されたメソッドを,インタフェースを実装するクラスでnative宣言又はsynchronized宣言されるメソッドで実装してもよい。

インタフェースで宣言されるメソッドをfinal宣言してはならないことに注意すること。そうでないときには,コンパイル時エラーが発生する。しかし,インタフェースで宣言されるメソッドを,インタフェースを実装するクラスでfinal宣言されるメソッドで実装してもよい。

9.4.1 継承及び上書き

インタフェースがメソッドを宣言すれば,そのメソッドの宣言は,このインタフェース内のコードにアクセス可能なそのインタフェースのスーパインタフェース内の同じシグネチャをもつ任意及びすべてのメソッドを,上書き(override)するという。

インタフェース内でのメソッド宣言が,他のインタフェース内でのメソッドの宣言を上書きする場合,各々のメソッドで異なった返却値の型をもつ,又は一つが返却値の型をもち他方が void ならば,コンパイル時エラーが発生する。さらにメソッド宣言は,それが上書きするいかなるメソッド宣言とも矛盾するthrows(8.4.4)をもってはならない。そうでないときには,コンパイル時エラーが発生する。

メソッドは,シグネチャ単位で上書きされる。例えば,もしインタフェースが同じ名前の二つのpublicなメソッドを宣言し,しかもサブインタフェースがそれらの一つを上書きすれば,そのサブインタフェースのもう一つのメソッドも継承する。

インタフェースは,インタフェース内での宣言によって上書きされないスーパインタフェースのすべてのメソッドを,その直接的スーパインタフェースから継承する。

インタフェースが,同じシグネチャ(8.4.2)の複数のメソッドを継承することは可能とする。この状況は,そのままではコンパイル時エラーを引き起こさない。インタフェースはすべてのメソッドを継承する。しかし,その継承された任意の二つのメソッドに対して,それらが違った返却値の型をもつか,又は一つが返却値の型をもち他方がvoidならば,コンパイル時エラーが発生する。(throws節は,この場合エラーを引き起こさない)。

同じメソッド宣言が一つのインタフェースから継承される経路は,複数存在することがある。この事実は,いかなる困難も引き起こさず,決してそのままではコンパイル時エラーを生じない。

9.4.2 オーバロード

インタフェースの二つのメソッドが(両方とも同じインタフェース内で宣言したか,両方とも一つのインタフェースによって継承されたか,又は,一つが宣言されもう一つが継承されたか,にかかわらず)同じ名前で異なったシグネチャをもてば,そのメソッド名はオーバロード(overload)されると言う。この事実は,どんな困難も引き起こさず,そのままでは決してコンパイル時エラーを生じない。同じ名前で異なったシグネチャをもつ二つのメソッドの返却値の型の間又はthrows節の間には,要求される関係は存在しない。

9.4.3 抽象メソッド宣言の例

次に抽象メソッド宣言に関するいくつかの(微妙な)点を例示する。

9.4.3.1 例:上書き

インタフェース内で宣言されるメソッドは,abstractで,いかなる実装も含まない。メソッドシグネチャを確定する以外に上書きメソッド宣言で達成できることは,メソッドの実装によって投げられるかもしれない例外を制限することがある。次に8.4.3.1で示す例の変形を示す。


class BufferEmpty extends Exception {
    BufferEmpty() { super(); }
    BufferEmpty(String s) { super(s); }
}

class BufferError extends Exception { BufferError() { super(); } BufferError(String s) { super(s); } }
public interface Buffer { char get() throws BufferEmpty, BufferError; }
public interface InfiniteBuffer extends Buffer { char get() throws BufferError; // override }

9.4.3.2 例:オーバロード


interface PointInterface {
    void move(int dx, int dy);
}

interface RealPointInterface extends PointInterface { void move(float dx, float dy); void move(double dx, double dy); }
前述の例では,メソッド名moveがインタフェースRealPointInterfaceで三つの違ったシグネチャ(二つは宣言されており一つは継承されている)でオーバロードされている。インタフェースRealPointInterfaceを実装するいかなるクラスも,三つのメソッドシグネチャすべての実装を提供しなければならない。


目次 | |