JAva 8デフォルトメソッドとマルチ継承
以前よく話されていた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というメソッドがない場合、コンパイルエラーが発生する.
エラーメッセージ:
サブインタフェースCでoverrideという方法を上書きすることで、コンパイルにエラーが発生しません.
注意メソッド署名には、メソッドの戻り値は含まれません.すなわち、戻り値が異なる2つのメソッドのみの署名も同じです.次のコードコンパイルでは、AとBのデフォルトメソッドが異なるため、Cは2つのデフォルトメソッドを暗黙的に継承します.
しかし、異なる署名の方法でも見分けがつかない場合があります.
Javaは最適な方法を選択します.Java仕様15.12.2.5を参照してください.
インタフェース多層継承
次は多層継承の問題を見てみましょう.継承関係は下図のように、A 2はA 1を継承し、CはA 2を継承する.
+---------------+ | Interface A1 | +--------+------+ | | | +--------+------+ | Interface A2 | +-------+-------+ | | | +-------+--------+ | Interface C | +----------------+
クラス継承に対する我々の認識に基づいて,直接定義されたデフォルトメソッド,上書きされたデフォルトメソッド,およびA 1インタフェースに暗黙的に継承されたデフォルトメソッドを含むCがA 2を継承するデフォルトメソッドを容易に知ることができる.
マルチレイヤ継承
上の例か単継承の例か、下図のように多継承すれば?
+---------------+
| Interface A1 |
+--------+------+
|
|
|
+--------+------+ +---------------+
| Interface A2 | | Interface B |
+-------+-------+ +---------+-----+
| +---------+---------^
| |
| |
+-------+-------++
| Interface C |
+----------------+
A 2とBが同じ署名方法を持っている場合、これは最初の例と同じです.エラーをコンパイルしたくない場合は、親インタフェースのデフォルトメソッドを上書きしたり、指定した親インタフェースのデフォルトメソッドを呼び出したりできます.
より複雑な多層多重継承
+--------------+
| Interface A1 |
+------+------++
| ^+-------+
| |
+-------+-------+ |
| Interface A2 | |
+------------+--+ |
^--++ |
| |
+--+------+-----+
| Interface C |
+---------------+
インタフェースA 2はA 1を継承し、インタフェースCはA 2とA 1を継承する.コードは次のとおりです.
以上のコードはコンパイルエラーが発生せず、出力A 2を実行します.
インタフェースCがサブインタフェースを暗黙的に継承する方法、すなわちサブインタフェースA 2のデフォルトの方法が見られる.
クラス継承
継承関係タイプがすべてクラスである場合、クラスは依然として単一の継承であるため、多くの継承の問題はありません.
クラスとインタフェースの混在
最初の例のインタフェースの1つをクラスに変えると、どのような現象が発生しますか.
+-------------+ +-----------+
| Interface A | | Class B |
+-----------+-+ +-----+-----+
^-+ +--+-----^
| |
+---+----+-+
| Class C |
+----------+
次のコードはコンパイルエラーが発生しません.
結果出力B.
子クラスが親クラスを優先的に継承する方法は、親クラスが同じ署名の方法を持っていない場合にインタフェースのデフォルトの方法を継承することがわかります.
結論
より複雑な継承関係は、以上の継承関係に簡略化することができる.以上の例から、以下の結論が得られる.
クラスはインタフェースよりも優れている.サブクラスが継承する親クラスとインタフェースが同じ方法で実装されている場合.では、サブクラスが親を継承するメソッドサブタイプのメソッドは、親タイプのメソッドよりも優先されます.上記の条件が満たされていない場合は、上書き/実装方法を表示するか、abstractと宣言する必要があります.
これらは古くから言われていますが、今年Java 8が発表されてから、インタフェースでもメソッドを定義することができます(default method).従来の設計を破ってインタフェースに具体的な方法を追加したのは,既存のJavaクラスライブラリのクラスに新しい機能を追加し,これらのクラスを再設計する必要がないためである.たとえば、Collectionインタフェースにdefault Stream
これはトレードオフの設計であり,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と宣言する必要があります.