JAva 8デフォルトメソッドとマルチ継承

7584 ワード

以前よく話されていたJava対比c++の利点の一つは、Javaにはあまり継承されていない問題である.Java中性子クラスは単一の親クラスしか継承できないため,複数のインタフェースを実装できるが,インタフェースには抽象的なメソッドしかなく,メソッド体は空であり,具体的なメソッド実装がなく,メソッド競合の問題はない.
        これらは古くから言われていますが、今年Java 8が発表されてから、インタフェースでもメソッドを定義することができます(default method).従来の設計を破ってインタフェースに具体的な方法を追加したのは,既存のJavaクラスライブラリのクラスに新しい機能を追加し,これらのクラスを再設計する必要がないためである.たとえば、Collectionインタフェースにdefault Streamstream()を追加するだけで、対応するSetインタフェースとListインタフェース、およびそれらのサブクラスには、サブクラスごとにこのメソッドを再copyする必要はありません.
        これはトレードオフの設計であり,Javaにマルチ継承を導入した問題をもたらす.インタフェースはインタフェースを継承することができ、クラスはクラスを継承し、インタフェースを実装することができることを知っています.継承されたクラスと実装されたインタフェースに同じ署名方法があると,どのような状況になるのだろうか.Javaマルチ継承のルールを明確に理解できるように,様々な状況のマルチ継承について検討する.
インタフェースは複数の親インタフェースを継承します
     3つのインタフェースInterface A,Interface B,Interface Cがあると仮定し,継承関係は以下の通りである.
+---------------+         +------------+ |  Interface A  |         |Interface B | +-----------^---+         +---^--------+                |                 |                         |                 |                         |                 |                      +-+------------+--+                        | Interface C|                           +------------+
      A,Bは同じ署名のデフォルトメソッドdefault String say(String name)を有し、インタフェースCにoverrideというメソッドがない場合、コンパイルエラーが発生する.
<span style="font-size:18px;"> interface A {
	default String say(String name) {
		return "hello " + name;
	}
}
interface B {
	default String say(String name) {
		return "hi " + name;
	}
}
interface C extends A,B{

}</span>

エラーメッセージ:
<span style="font-size:18px;">C:/Lambda/src>javac -J-Duser.country=US com/colobu/lambda/chap
ter3/MultipleInheritance1.java
com/colobu/lambda/chapter3/MultipleInheritance1.java:17: error: interface C inherits unrelated defaults for say(String) from types A and B
        static interface C extends A,B{
               ^
       1 error</span>

        サブインタフェースCでoverrideという方法を上書きすることで、コンパイルにエラーが発生しません.
<span style="font-size:18px;">interface C extends A,B{
	default String say(String name) {
		return "greet " + name;
	}
}</span>

       注意メソッド署名には、メソッドの戻り値は含まれません.すなわち、戻り値が異なる2つのメソッドのみの署名も同じです.次のコードコンパイルでは、AとBのデフォルトメソッドが異なるため、Cは2つのデフォルトメソッドを暗黙的に継承します.
<span style="font-size:18px;">interface A {
	default void say(int name) {

	}
}
interface B {
	default void say(String name) {

	}
}
interface C extends A,B{

}</span>

しかし、異なる署名の方法でも見分けがつかない場合があります.
<span style="font-size:18px;">interface A {
	default void say(int a) {
		System.out.println("A");
	}
}
interface B {
	default void say(short a) {
		System.out.println("B");
	}
}
interface C extends A,B{

}
static class D implements C {

}
public static void main(String[] args) {
	D d = new D();
	byte a = 1;
	d.say(a); //B
}</span>

Javaは最適な方法を選択します.Java仕様15.12.2.5を参照してください.
インタフェース多層継承
次は多層継承の問題を見てみましょう.継承関係は下図のように、A 2はA 1を継承し、CはA 2を継承する.
+---------------+  |  Interface A1 |  +--------+------+           |                  |                  |         +--------+------+  |  Interface A2 |  +-------+-------+          |                  |                  |          +-------+--------+ |   Interface C  | +----------------+
       クラス継承に対する我々の認識に基づいて,直接定義されたデフォルトメソッド,上書きされたデフォルトメソッド,およびA 1インタフェースに暗黙的に継承されたデフォルトメソッドを含むCがA 2を継承するデフォルトメソッドを容易に知ることができる.
<span style="font-size:18px;">interface A {
	default void say(int a) {
		System.out.println("A");
	}

	default void run() {
		System.out.println("A.run");
	}
}
interface B extends A{
	default void say(int a) {
		System.out.println("B");
	}


	default void play() {
		System.out.println("B.play");
	}
}
interface C extends A,B{

}</span>

マルチレイヤ継承
       上の例か単継承の例か、下図のように多継承すれば?
+---------------+                          
|  Interface A1 |                          
+--------+------+                          
         |                                 
         |                                 
         |                                 
+--------+------+         +---------------+
|  Interface A2 |         |  Interface B  |
+-------+-------+         +---------+-----+
        |       +---------+---------^      
        |       |                          
        |       |                          
+-------+-------++                         
|   Interface C  |                         
         +----------------+
        A 2とBが同じ署名方法を持っている場合、これは最初の例と同じです.エラーをコンパイルしたくない場合は、親インタフェースのデフォルトメソッドを上書きしたり、指定した親インタフェースのデフォルトメソッドを呼び出したりできます.
<span style="font-size:18px;">interface A1 {
		default void say(int a) {
			System.out.println("A1");
		}
	}


	interface A2 extends A1 {


	}


	interface B {
		default void say(int a) {
			System.out.println("B");
		}
	}
	interface C extends A2,B{
		default void say(int a) {
			B.super.say(a);
		}
	}</span>

より複雑な多層多重継承
+--------------+              
 | Interface A1 |              
 +------+------++              
              |          ^+-------+      
              |                       |      
+-------+-------+            |      
|  Interface A2 |           |      
+------------+--+            |      
              ^--++              |      
                      |              |      
              +--+------+-----+
              |  Interface C  |
              +---------------+
       インタフェースA 2はA 1を継承し、インタフェースCはA 2とA 1を継承する.コードは次のとおりです.
<span style="font-size:18px;">interface A1 {
	default void say() {
		System.out.println("A1");
	}
}
interface A2 extends A1 {
	default void say() {
		System.out.println("A2");
	}
}
interface C extends A2,A1{


}
static class D implements C {


}
public static void main(String[] args) {
	D d = new D();
	d.say();
}</span>

以上のコードはコンパイルエラーが発生せず、出力A 2を実行します.
インタフェースCがサブインタフェースを暗黙的に継承する方法、すなわちサブインタフェースA 2のデフォルトの方法が見られる.
クラス継承
         継承関係タイプがすべてクラスである場合、クラスは依然として単一の継承であるため、多くの継承の問題はありません.
クラスとインタフェースの混在
      最初の例のインタフェースの1つをクラスに変えると、どのような現象が発生しますか.
+-------------+       +-----------+
| Interface A |       |  Class B  |
+-----------+-+       +-----+-----+
            ^-+    +--+-----^      
              |    |               
          +---+----+-+             
          |  Class C |             
          +----------+
次のコードはコンパイルエラーが発生しません.
<span style="font-size:18px;">interface A {
	default void say() {
		System.out.println("A");
	}
}
static class B {
	public void say() {
		System.out.println("B");
	}
}
static class C extends B implements A{


}
public static void main(String[] args) {
	C c = new C();
	c.say(); //B
}</span>

結果出力B.
      子クラスが親クラスを優先的に継承する方法は、親クラスが同じ署名の方法を持っていない場合にインタフェースのデフォルトの方法を継承することがわかります.
結論
       より複雑な継承関係は、以上の継承関係に簡略化することができる.以上の例から、以下の結論が得られる.
      クラスはインタフェースよりも優れている.サブクラスが継承する親クラスとインタフェースが同じ方法で実装されている場合.では、サブクラスが親を継承するメソッドサブタイプのメソッドは、親タイプのメソッドよりも優先されます.上記の条件が満たされていない場合は、上書き/実装方法を表示するか、abstractと宣言する必要があります.