そもそも多重継承とは?
継承とは?
まず、前提として継承には以下の目的があります。(参考:http://equj65.net/tech/java8mixin/)
- 実装の継承(使い回し)
- 仕様の継承(継承したクラスが、あるメソッドを持つことを明示的に指定する。つまりクライアント側から見ると、あるメソッドが必ず実装されていることが確約されている状態)
実際の継承パターンとしては、以下の二パターンです。
- 仕様と実装を両方継承する
- 仕様のみ継承する。
open class Person {
fun greet() { System.out.print("こんにちは") }
}
class Japanese: Person{ }
fun main(args: Array<String>) {
val taro = Japanese()
taro.greet()
}
interface Person {
fun greet()
}
class Japanese: Person{
override fun greet() { System.out.print("こんにちは") }
}
fun main(args: Array<String>) {
val taro = Japanese()
taro.greet()
}
上記の通り、仕様のみを継承する場合はinterfaceを継承します。
interfaceは実装を含むことができないためです。(Java8で実装を持てるようになったようですが、ここでは触れません)
多重継承とは?
文字通り複数のクラスから継承することを指します。
What? 問題は?
菱型問題(Diamond problem)と呼ばれる現象がある。
以下のコードを見てください。
interface Person {
fun greet()
}
open class Japanese: Person {
override fun greet() { System.out.print("こんにちは") }
}
class American: Person {
override fun greet() { System.out.print("Hello") }
}
// もしこのような多重継承が可能だとしたら
class Half: Japanese(), American { }
fun main(args: Array<String>) {
val karl = Half()
karl.greet()
}
果たしてkarlはどんな挨拶をするのでしょうか?
これは実際にはコンパイルエラーになります。Javaでは複数の親クラスを持つことはできないためです。
なぜ複数の親クラスを持つことができないのでしょうか。
上記の例のように継承した親クラス(Japanese, American)が同一名称のメソッドを実装していた場合を考えます。
継承するクラス(Half)からgreetメソッドを呼び出したときにどちらのメソッドが呼ばれるのかをコンパイラは解決することができません。
このため、コンパイラ的な都合でJavaでは複数のクラス(!= interface)を継承することはできません。
多重継承を実現するためには
実装クラスを継承する代わりにinterfaceを継承します。
ただし、この場合は継承する先のクラスに実装を書かなければなりません。
そのため、実装の使い回しは行えず、仕様の継承のみが可能になります。
考察
仕様の継承は、Rubyで言う所のDuck Typingに通じるものがありそう。ただし、以下の点で仕様の継承の方が優秀だと思う。
- クラスの作成者に実装を強制できる
- クライアント側は実装があることが前提であるため、余計なエラーハンドリングなどを考える必要がない
- 型チェックも行えるので、より確実な開発が行える
実装の継承は、Rubyで言う所のMix-inに通じるものがありそう。ただし、Mix-inの方が継承構造が複雑にならない点でより気軽に使える。
参考
http://equj65.net/tech/java8mixin/
https://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html
https://en.wikipedia.org/wiki/Multiple_inheritance
https://en.wikipedia.org/wiki/Mixin