S-JIS[2014-03-22/2015-06-14] 変更履歴

Javaインターフェース

Javaのインターフェースについて。


インターフェース

インターフェースは、理屈としては、抽象クラスの特殊形態。[2003-07-06/2007-05-02]
抽象クラスのうち、抽象メソッドと定数だけが定義できるようなもの。(JDK1.8でデフォルトメソッドが定義できるようになったが)
つまり、実体(実際の動作)を持たず、「こういう形でメソッドを呼び出してね」という形式(インターフェース)だけを定義する。
使い道が独特なので、クラスとは別の文法になっている。

publicinterface インターフェース名 〔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]
例外の基礎

インターフェースの使用法
妙:インターフェースを後から適用


インターフェースのstaticメソッド

JDK1.8から、インターフェース上にstaticメソッドを定義できるようになった。[2014-03-22]

public interface インターフェース {

	〔public〕 static 戻り型 メソッド名(引数,…) {
		〜
	}
}

ちなみに、これによって「public static void main(String... args)」もインターフェースで定義できるようになった。
javaコマンドでインターフェース名を指定すれば、ちゃんと実行できる!


呼び出すときは(クラスのstaticメソッドと同様に)インタフェース名.メソッド名(〜)で呼び出せる。
ただし、クラスのstaticメソッド(やインターフェースのstatic定数)とは異なり、継承したインターフェースやクラスの名前を使って呼び出すことは出来ない(コンパイルエラーになる)。

状況 定義例 使用例 備考
インターフェース
staticメソッド
interface A1 {
  public static int getValue() {
    return 123;
  }
}

interface B1 extends A1 {}
A1.getValue()
B1.getValue()

×
 
class C1 implements A1 {} C1.getValue() ×  
class D1 implements A1 {
  public static void method() {
    System.out.println(getValue());
  }
}
  × A1.getValue()」なら可。
けど、それは継承とは関係ない指定方法だよね^^;
クラス
staticメソッド
class A2 {
  public static int getValue() {
    return 123;
  }
}

class B2 extends A2 {}
A2.getValue()
B2.getValue()

 
class D2 extends A2 {
  public static void method() {
    System.out.println(getValue());
  }
}
   
インターフェース
staticフィールド
(定数)
interface A3 {
  public static final int VALUE = 123;
}

interface B3 extends A3 {}
A3.VALUE
B3.VALUE

 
class C3 implements A3 {} C3.VALUE  
class D3 implements A3 {
  public static void method() {
    System.out.println(VALUE);
  }
}
   

インターフェースのデフォルトメソッド

JDK1.8から、インターフェースに処理本体が記述できるメソッド(抽象でないメソッド)が定義できるようになった。[2014-03-22]
これをデフォルトメソッドと呼ぶ。

public interface インターフェース {

	〔public〕〔abstract〕 default 戻り型 メソッド名(引数,…) {
		〜
	}
}

インターフェースを実装したクラスからは、普通のメソッドのように呼び出したりオーバーライドしたりすることが出来る。

ただし、同じシグネチャー(メソッド名と引数の型が同じ)を持つ別々のインターフェースを継承しようとすると、そのままではコンパイルエラーになる。
オーバーライドして定義し直せば使うことが出来る。


状況 定義例 使用例 備考
シンプルな例 interface A {
  public default int getValue() {
    return 123;
  }
}

class B implements A {}
B b = new B();
int n = b.getValue();
 
多重継承
(エラー)
interface A1 {
  public default int getValue() {
    return 123;
  }
}

interface A2 {
  public default int getValue() {
    return 123;
  }
}

interface C extends A1, A2 {}
  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 {
  @Override
  public default int getValue() {
    return A1.super.getValue();
  }
}
  複数の継承元に同一シグネチャーのデフォルトメソッドが存在している場合は、
そのメソッドをオーバーライドして実装し直せばよい。
継承元のどちらかのメソッドを呼び出せばいい場合は、
親インターフェース名.super.メソッド名
でインターフェース名を指定して呼び出すことが出来る。
class E implements A1, A2 {
  @Override
  public int getValue() {
    return A1.super.getValue();
  }
}
 
孫継承 interface A3 extends A2 {}

class F implements A1, A3 {
  @Override
  public int getValue() {
//  return A1.super.getValue();
    return A2.super.getValue();
//  return A3.super.getValue();
  }
}
  superでは親の親を指定することは出来ない。
左記の例ではA1とA3を直接実装しているのでA1.superA3.superは指定可能だが、
A2は直接実装していないのでA2.superはコンパイルエラーになる。
インターフェースと
クラス間の重複
class G {
  public int getValue() {
    return 789;
  }
}

class H1 extends G implements A1 {}
H1 h1 = new H1();
int n = h1.getValue();

→789が返る
親クラスと親インターフェースで同一シグネチャーのメソッドが存在している場合は、
親クラスのメソッドが有効になる。
class G {
  protected int getValue() {
    return 789;
  }
}

class H1 extends G implements A1 {}
  ただし、親クラスのメソッドが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 {
  @Override
  public int getValue() {
//  return super.getValue();
//×return G.super.getValue();
    return A1.super.getValue();
  }
}
H2 h2 = new H2();
int n = h2.getValue();

→123が返る
親クラスと親インターフェースで同一シグネチャーのメソッドが存在している場合でも、
親インターフェース名.super.メソッド名
という記法は可能。
ただし「親クラス名.super.メソッド名」とは書けない。
親クラスのメソッドを指定する場合は(従来通り)「super.メソッド名」と書く。

デフォルトメソッドとして定義できないメソッド

java.lang.Objectクラスに存在する(private以外の)メソッド(toString, equals, hashCode)は、デフォルトメソッドとして定義できない。[2015-06-14]

エラーメッセージ 説明
interface I1 {
  public default String toString() {
    return "I.default";
  }
}
A default method cannot override a method from java.lang.Object toString, equals, hashCodeメソッドはデフォルトメソッドとして定義できない。
interface I2 {
  public default Class<?> getClass() {
    return I.class;
  }
}
Cannot override the final method from Object getClass, wait, notify, notifyAllメソッドは、finalメソッドなのでデフォルトメソッドとして定義できない。
(オーバーライドできない)
interface I3 {
  public default Object clone() throws CloneNotSupportedException{
    System.out.println("I.clone");
    return null;
  }
}
  (Objectでprotectedとして定義されている)clone, finalizeメソッドは、デフォルトメソッドとして定義できる。
が、そのインターフェースを実装するクラスの側では、デフォルト実装として使用できない。
インターフェースとクラス間の重複の問題
class J3 implements I {
}
The inherited method Object.clone() cannot hide the public abstract method in I3

Scalaのトレイトとの違い

インターフェースがデフォルトの実装を持てるようになったことでScalaのトレイトに近づいたが、
Scalaのトレイトはフィールドを定義できるし、メソッドやフィールドの可視性をprotectedにすることも出来る。


特殊な用途のインターフェース

(文法としては普通のインターフェースなのだが)特別な使い方をするインターフェースについては名前が付いている。[2014-03-22]

呼び名 説明
マーカーインターフェース
(marker interface)
フィールドやメソッドを一切定義しないインターフェース。
マーカーインターフェースを実装することによって何らかの意図を示す為に使う。
例:Serializableインターフェースはシリアライズ可能であることを示す
例:Cloneableインターフェースはクローン可能であることを示す

JDK1.5でアノテーションが導入された為、JDK1.5以降ではマーカーインターフェースでなくアノテーションを使う。
(SerializableはJDK1.4以前から在るので仕方ないが、本来であればSerializableアノテーションであるべき)

定数インターフェース
(constant interface)
定数(staticフィールド)を定義する目的で用意しているインターフェース。
定数インターフェースを用意し、使う箇所でそれぞれ実装することで、定数を共有することが出来る。

JDK1.5ではstaticインポートが出来るようになったので、定数定義はクラスでも出来る。
インターフェースを実装する方式だとJavadocにも出てきてしまうので、staticインポートの方が良い。

関数型インターフェース
(functional interface)
抽象メソッドが1つだけ定義されているインターフェース。(JDK1.8でそう呼ばれるようになった)
JDK1.8では、関数型インターフェースにはラムダ式メソッド参照コンストラクター参照を渡すことが出来る。

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま