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 voidmain(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メソッドは、デフォルトメソッドとして定義できる。 が、そのインターフェースを実装するクラスの側では、デフォルト実装として使用できない。 →インターフェースとクラス間の重複の問題 | |
classJ3 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では、関数型インターフェースにはラムダ式やメソッド参照・コンストラクター参照を渡すことが出来る。 |