Javaのインターフェースについて。
|
インターフェースは、理屈としては、抽象クラスの特殊形態。[2003-07-06/2007-05-02]
抽象クラスのうち、抽象メソッドと定数だけが定義できるようなもの。(JDK1.8でデフォルトメソッドが定義できるようになったが)
つまり、実体(実際の動作)を持たず、「こういう形でメソッドを呼び出してね」という形式(インターフェース)だけを定義する。
使い道が独特なので、クラスとは別の文法になっている。
〔public〕 interface インターフェース名 〔extends 親インターフェース名,…〕 { インターフェース本体 }
インターフェースを別のインターフェースから派生(別のインターフェースを継承)する場合はextendsで親インターフェースを指定する。
extendsを書かない場合でも、クラスの場合と同様、Objectから派生したような扱いになる。[2008-08-30]
すなわち、Objectクラスのpublic(でfinalでない)メソッドが暗黙に定義される。
(だからインターフェースの変数に対してtoString()を呼び出したり出来る)
インターフェース本体の定義は、以下のように行う。
interface インターフェース名 { public static final 型 定数名 = 初期値; public abstract 型 メソッド名(引数,…); ←抽象メソッドそのもの }
インターフェースは他のクラスから呼び出す際に使用するものなので、インターフェースのメンバー(フィールド・メソッド)は必ずpublicになる。(それ以外はコンパイルエラー)
publicを省略するとpublicが指定されたことになる。(クラスの場合は、省略するとパッケージプライベートになる)
「public static final」や「abstract」を省略して書くことも出来るが、暗黙にそれらが指定されたものとして扱われる。
(定数の「public static final」は全部書くけど、メソッドの「abstract」は省略することが多い。
なお、「それしか指定できないんだから一々書かずに全部省略せよ」という人と「勘違いさせない為に全部書け」という人がいる)
個人的には、定数を書く際は「public static final」をきちんと書くべきと考える。[2010-06-27]
なぜなら、省略した場合の意味が、classとinterfaceでは異なるから。
interface A { int Z = 0; } |
class A { int Z = 0; } |
interfaceでは、フィールドZはパブリックな定数。 classでは、パッケージプライベートの変数。 |
interface A { public static final int Z = 0; } |
class A { public static final int Z = 0; } |
「public static final」を付けておけば、どちらも同じ意味になる。 誤解が無いし、コピー&ペーストも問題なく行える。 |
また、メソッドのabstractは省略する・publicは付けるのが好み。[2010-06-27]
なぜなら、そのインターフェースを継承した具象クラスを書く場合に、メソッド定義をコピー&ペーストするのが楽だから。abstractを消す手間だけの問題だけど^^;
抽象クラスの場合はabstractが必須なので、そちらと合わせて常にabstractを付けるべきなのかもしれないが…
public static finalの場合とは異なり、抽象クラスではabstractが付いていないとコンパイルエラーになるので、誤解の度合いが違う。
interface A { int method(); } |
class A { int method(); } |
classではメソッドの本体を書かないと (あるいはabstractを付けないと) コンパイルエラーになる。 |
interface A { int method(); } |
abstract class A { abstract int method(); } |
interfaceはpublicだが、classではパッケージプライベートになる。 |
interface A { public int method(); } |
abstract class A { public abstract int method(); } |
「public」を付けておけば、誤解が無い。 |
インターフェースを実装する際は、implementsを使う。1つのクラスで複数のインターフェースを実装することが出来る。
実装されないメソッドがあると、コンパイルエラーとなる。(abstract classの場合はエラーにならない。が、さらに派生したクラスで、最終的には全部実装しないといけない…→newでインスタンス化できない)
class ほげほげ extends むにゃ implements 名前A, 名前B { 〜 }
interface I {
public int method();
}
class C implements I {
@Override //JDK1.6から、インターフェースを実装した際にも@Overrideアノテーションが付けられるようになった
public int method() {
return 1;
}
}
実装したメソッドの戻り値の型は、インターフェースで定義されている型と同じにする。
が、JDK1.5以降では、派生クラスを返すように定義できるようになった。[2010-06-27]
→共変戻り値型
インターフェースでthrowsが宣言されている場合、具象クラスではその例外を減らしてもいい(増やすのは駄目)。[2010-06-27]
→例外の基礎
→インターフェースの使用法
→妙:インターフェースを後から適用
JDK1.8から、インターフェース上にstaticメソッドを定義できるようになった。[2014-03-22]
public interface インターフェース { 〔public〕 static 戻り型 メソッド名(引数,…) { 〜 } }
ちなみに、これによって「public static void main(String... args)
」もインターフェースで定義できるようになった。
javaコマンドでインターフェース名を指定すれば、ちゃんと実行できる!
呼び出すときは(クラスのstaticメソッドと同様に)インタフェース名.メソッド名(〜)
で呼び出せる。
ただし、クラスのstaticメソッド(やインターフェースのstatic定数)とは異なり、継承したインターフェースやクラスの名前を使って呼び出すことは出来ない(コンパイルエラーになる)。
状況 | 定義例 | 使用例 | 備考 | |
---|---|---|---|---|
インターフェース staticメソッド |
interface A1 { |
A1.getValue() |
○ × |
|
class C1 implements A1 {} |
C1.getValue() |
× | ||
class D1 implements A1 { |
× | 「A1.getValue() 」なら可。けど、それは継承とは関係ない指定方法だよね^^; |
||
クラス staticメソッド |
class A2 { |
A2.getValue() |
○ ○ |
|
class D2 extends A2 { |
○ | |||
インターフェース staticフィールド (定数) |
interface A3 { |
A3.VALUE |
○ ○ |
|
class C3 implements A3 {} |
C3.VALUE |
○ | ||
class D3 implements A3 { |
○ |
JDK1.8から、インターフェースに処理本体が記述できるメソッド(抽象でないメソッド)が定義できるようになった。[2014-03-22]
これをデフォルトメソッドと呼ぶ。
public interface インターフェース { 〔public〕〔abstract〕 default 戻り型 メソッド名(引数,…) { 〜 } }
インターフェースを実装したクラスからは、普通のメソッドのように呼び出したりオーバーライドしたりすることが出来る。
ただし、同じシグネチャー(メソッド名と引数の型が同じ)を持つ別々のインターフェースを継承しようとすると、そのままではコンパイルエラーになる。
オーバーライドして定義し直せば使うことが出来る。
状況 | 定義例 | 使用例 | 備考 |
---|---|---|---|
シンプルな例 | interface A { |
B b = new B(); |
|
多重継承 (エラー) |
interface A1 { |
Cはコンパイルエラーになる。 「Duplicate default methods named getValue with the parameters () and () are inherited from the types A2 and A1」 Cの継承元であるA1とA2に同一シグネチャーのデフォルトメソッドが存在している為。 |
|
多重継承 (オーバーライド) |
interface D extends A1, A2 { |
複数の継承元に同一シグネチャーのデフォルトメソッドが存在している場合は、 そのメソッドをオーバーライドして実装し直せばよい。 継承元のどちらかのメソッドを呼び出せばいい場合は、 「 親インターフェース名.super.メソッド名 」でインターフェース名を指定して呼び出すことが出来る。 |
|
class E implements A1, A2 { |
|||
孫継承 | interface A3 extends A2 {} |
superでは親の親を指定することは出来ない。 左記の例ではA1とA3を直接実装しているので A1.super とA3.super は指定可能だが、A2は直接実装していないので A2.super はコンパイルエラーになる。 |
|
インターフェースと クラス間の重複 |
class G { |
H1 h1 = new H1(); →789が返る |
親クラスと親インターフェースで同一シグネチャーのメソッドが存在している場合は、 親クラスのメソッドが有効になる。 |
class G { |
ただし、親クラスのメソッドがprotectedの場合はコンパイルエラーになる。[2015-06-14] 「The inherited method G.getValue() cannot hide the public abstract method in A1」 インターフェースと親クラスに同一シグネチャーのメソッドがあると親クラス優先だが、親クラスの当該メソッドの可視性はprotected。 一方、インターフェースではpublicだが、継承する際、publicをprotectedにする(可視性の範囲を狭くする)ことは出来ない。 ので、このようなエラーになるのだろう。 |
||
class H2 extends G implements A1 { |
H2 h2 = new H2(); →123が返る |
親クラスと親インターフェースで同一シグネチャーのメソッドが存在している場合でも、 「 親インターフェース名.super.メソッド名 」という記法は可能。 ただし「 親クラス名.super.メソッド名 」とは書けない。親クラスのメソッドを指定する場合は(従来通り)「 super.メソッド名 」と書く。 |
java.lang.Objectクラスに存在する(private以外の)メソッド(toString, equals, hashCode)は、デフォルトメソッドとして定義できない。[2015-06-14]
例 | エラーメッセージ | 説明 |
---|---|---|
interface I1 { |
A default method cannot override a method from
java.lang.Object |
toString, equals, hashCodeメソッドはデフォルトメソッドとして定義できない。 |
interface I2 { |
Cannot override the final method from Object |
getClass, wait, notify,
notifyAllメソッドは、finalメソッドなのでデフォルトメソッドとして定義できない。 (オーバーライドできない) |
interface I3 { |
(Objectでprotectedとして定義されている)clone,
finalizeメソッドは、デフォルトメソッドとして定義できる。 が、そのインターフェースを実装するクラスの側では、デフォルト実装として使用できない。 →インターフェースとクラス間の重複の問題 |
|
class J3 implements I3 { |
The inherited method Object.clone() cannot hide the public abstract method in I3 |
インターフェースがデフォルトの実装を持てるようになったことでScalaのトレイトに近づいたが、
Scalaのトレイトはフィールドを定義できるし、メソッドやフィールドの可視性をprotectedにすることも出来る。
Java9から、インターフェースにprivateメソッドが定義できるようになった。[2017-09-23]
public interface インターフェース { private 〔static〕 戻り型 メソッド名(引数,…) { 〜 } }
privateなので、インターフェース外部や継承したクラスから呼び出すことは出来ない。
同一インターフェース内のstaticメソッドやデフォルトメソッドから使う想定なのだろう。
(文法としては普通のインターフェースなのだが)特別な使い方をするインターフェースについては名前が付いている。[2014-03-22]
呼び名 | 説明 |
---|---|
マーカーインターフェース (marker interface) |
フィールドやメソッドを一切定義しないインターフェース。 マーカーインターフェースを実装することによって何らかの意図を示す為に使う。 例:Serializableインターフェースはシリアライズ可能であることを示す 例:Cloneableインターフェースはクローン可能であることを示す
JDK1.5でアノテーションが導入された為、JDK1.5以降ではマーカーインターフェースでなくアノテーションを使う。 |
定数インターフェース (constant interface) |
定数(staticフィールド)を定義する目的で用意しているインターフェース。 定数インターフェースを用意し、使う箇所でそれぞれ実装することで、定数を共有することが出来る。 JDK1.5ではstaticインポートが出来るようになったので、定数定義はクラスでも出来る。 |
関数型インターフェース (functional interface) |
抽象メソッドが1つだけ定義されているインターフェース。(JDK1.8でそう呼ばれるようになった) JDK1.8では、関数型インターフェースにはラムダ式やメソッド参照・コンストラクター参照を渡すことが出来る。 |