Kotlinのインタフェースの使い方はリスト6のようになります。
//インタフェースの定義例 interface Flyable{ fun fly() } //インタフェースの実装例 class Airplane: Vehicle(), Flyable{ override fun run(){ println("Running on runway!") } //インタフェースのメソッドを実装(実装しないとコンパイルエラー) override fun fly(){ println("Flying!") } } …… //インタフェースを実装したクラスの使用 val airplane = Airplane() airplane.run() //「Running on runway!」 airplane.fly() //「Flying!」
インタフェースを実装する際に、Javaの「implements」キーワードではなく、クラス継承と同様に「:」の後に記述する点に注意してください。また、インタフェースのメソッドを実装する際もoverrideキーワードを付けてオーバーライドを明示的に宣言する必要があります。
また、Java 8でインタフェースのデフォルト実装がサポートされましたが、Kotlinでもリスト7のように実装を持つインタフェースを使用できます。
//実装を持つインタフェースの定義例 interface Stoppable{ fun stop(){ print("Stopped slowly...") } } //実装を持つインタフェースの実装例 class StoppableVehicle: Vehicle(), Stoppable{ override fun run(){ println("Running!") } }
さて、Javaのインタフェースは基本的にメソッドを記述するのが中心で、フィールドについては全て定数で、自動的に「final public static」が指定されることになっています。つまり、インスタンスごとのフィールドを宣言することはできません。
一方Kotlinでは、リスト8のようにインタフェースにもプロパティを持つことができます。
//プロパティを持つ(!)インタフェース interface Limiter{ //プロパティを定義できるが、実際には「abstract」として扱われる val maxSpeed: Int //デフォルト実装からプロパティを参照できる fun boost(speed : Int){ if(speed > maxSpeed){ println("Overspeed!") }else{ println("Boosted to $speed") } } } //プロパティを持つインタフェースの実装例 class CarWithLimiter: Vehicle(), Limiter{ //インタフェースのプロパティを明示的に実装する必要あり override var maxSpeed : Int = 100 override fun run(){ println("Running!") } } …… //プロパティを持つインタフェースを実装したクラスの使用 val carWithLimiter = CarWithLimiter() carWithLimiter.run() //「Running!」 carWithLimiter.boost(120) //「Overspeed!」 //プロパティの内容がインスタンスごとであることを確認するため、もう1つインスタンスを作成 val carWithLimiter2 = CarWithLimiter() //1つ目のインスタンスのmaxSpeedプロパティを書き換える carWithLimiter.maxSpeed = 200 carWithLimiter.boost(120) //「Boosted to 120」maxSpeedが書き換わっているので問題なくブースト可能 carWithLimiter2.boost(120) //「Overspeed!」2つ目のインスタンスのmaxSpeedは書き換わっていない
ここでは、乗り物の制限速度機能を表す「Limiter」インタフェースに最高速度の「maxSpeed」プロパティを宣言しています。加速するための「boost」メソッドではmaxSpeedプロパティを参照して、指定速度まで加速可能かどうかを判定しています。
もちろん、Kotlinのインタフェースの仕組みもJavaと同様、実際には状態を持つことはできませんので、インタフェースに宣言するプロパティはアクセサ(getter、setter)を持つか、あるいは「abstract」でなければなりません。
LimiterインタフェースのmaxSpeedプロパティにはアクセサを指定していないため、abstractとして扱われます。そのため、Limiterインタフェースを実装する「CarWithLimiter」クラスで、maxSpeedプロパティをoverrideキーワード付きで継承することで、実際に値を保存する場所を確保しています。
このサンプルでは、maxSpeedプロパティがインスタンスごとに値を持っていることを確認するため、2つのCarWithLimiterインスタンスを使っています。
さて、インタフェースのデフォルト実装はとても便利な機能ですが、複数のインタフェースを実装する際、リスト9のようにデフォルト実装したメソッド名がぶつかることがあります。
interface Stoppable{ fun stop(){ print("Stopped slowly...") } } //同名の「stop」メソッドを持つインタフェース interface Stoppable2{ //名前のぶつかるメソッド fun stop(){ print("Stopped suddenly!!!") } //名前のぶつからないメソッド fun crash(){ print("Crash!!!") } } …… //同じ名前のメソッドを持つインタフェースを複数実装した例 class StoppableVehicle2: Vehicle(), Stoppable, Stoppable2{ override fun run(){ println("Running!") } //stopメソッドを定義しないとコンパイルエラーになる override fun stop(){ print("Original stop implementation!") super<Stoppable>.stop() //Stoppableのデフォルト実装を呼び出す super<Stoppable2>.stop() //Stoppable2のデフォルト実装を呼び出す } } …… //デフォルト実装の競合解決例 var stoppableVehicle2 = StoppableVehicle2() stoppableVehicle2.stop() //「Original stop implementation!Stopped slowly...Stopped suddenly!!!」
このサンプルでは「Stoppable」インタフェースと「Stoppable2」インタフェースで同名の「stop」というメソッドが両方ともデフォルト実装を持ち、名前が競合しています。通常インタフェースにデフォルト実装があれば、インタフェースを実装したクラスでそのメソッドをオーバーライドしなくても問題ありませんが、名前が競合している場合は、このサンプルのように、明示的にメソッドをオーバーライドする必要があります。
ここでは、「StoppableVehicle2」クラスでstopメソッドを明示的にオーバーライドし、その中でsuperキーワードを使って2つのインタフェースのデフォルト実装を呼び出しています。「super<インタフェース名>.メソッド名()」とすることで、実装しているインタフェースのうち、呼び出すインタフェースを指定できます*2。
*2)Java 8でもデフォルト実装のために競合解決が必要ですが、同様のケースでJavaで呼び出すインタフェースを指定する場合は「インタフェース名.super.メソッド名()」となる点が少し異なります。
デフォルト実装によってインタフェースの活用範囲が広がりますが、このような競合解決の必要性も出てきますので、使用に際してはメリット、デメリットを考慮したいですね。
Copyright © ITmedia, Inc. All Rights Reserved.