Kotlin
interface

Kotlinにおける実装のあるinterfaceの仕組み

More than 1 year has passed since last update.

ことの発端

以前、Android6(Marshmallow)のランタイムパーミッションを処理しやすくするために、ヘルパーを作ってみたときに、Kotlinだったら、もっと簡単に書けるんじゃないかと思って、こんなコードを書いてみました。

PermissionHelper.kt
interface PermissionHelper: ActivityCompat.OnRequestPermissionsResultCallback {

    fun execute(activity: Activity) {
        if (ContextCompat.checkSelfPermission(activity, PERMISSION) != PackageManager.PERMISSION_GRANTED) {
            // 以前に許諾して、今後表示しないとしていた場合は、ここにはこない
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, PERMISSION) && message != null) {
                // ユーザに許諾してもらうために、なんで必要なのかを説明する
                val builder = AlertDialog.Builder(activity)
                builder.setMessage(message)
                builder.setPositiveButton(if (caption == null) "OK" else caption) { dialog, which ->
                    //  許諾要求
                    requestPermission(activity)
                }
                builder.show()
            } else {
                //  許諾要求
                requestPermission(activity)
            }
        } else {
            // 許諾されているので、やりたいことをやる
            onAllowed()
        }
    }

    private fun requestPermission(activity: Activity) {
        val permissions = arrayOf(PERMISSION)
        ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
    }

    val message: String?
    val caption: String?
    val REQUEST_CODE: Int
    val PERMISSION: String

    fun onAllowed()
    fun onDenied() {}

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        if (requestCode == REQUEST_CODE) {
            if (permissions.size > 0 && permissions[0] == PERMISSION && grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 許諾されたので、やりたいことをやる
                onAllowed()
            } else {
                onDenied()
            }
        }
    }
}

MainActivity.kt
class MainActivity : AppCompatActivity(), PermissionHelper {

    override val message: String? get() = null
    override val caption: String? get() = null
    override val REQUEST_CODE: Int get() = 123
    override val PERMISSION: String get() = Manifest.permission.WRITE_EXTERNAL_STORAGE

    val takePhoto1: Button by bindView<Button>(R.id.take_photo_1)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        takePhoto1.setOnClickListener {
            execute(this)
        }
    }

    override fun onAllowed() {
        throw UnsupportedOperationException()
    }
}

ところが、onRequestPermissionsResult()がどーたらこーたらでエラーになってしまって、PermissionHelperで実装してるはずなのにー、それって何処行っちゃったの?
という疑問がわいたので、もっと単純な構成で、確認してみました。

コンパイルしてデコンパイル

Foo.kt
interface Foo {

    fun foo(): String
}
Bar.kt
interface Bar: Foo {

    fun bar(): String;

    override fun foo(): String {
        return bar();
    }
}
Buzz.kt
class Buzz: Bar {

    override fun bar(): String {
        return "Buzz"
    }
}

を用意して、コンパイルした後、できあがったclassファイルをデコンパイルしてみます。

Foo.java
public interface Foo {

    public abstract String foo();
}
Bar.java
public interface Bar extends Foo {

    public static final class DefaultImpls {

        public static String foo(Bar $this) {
            return $this.bar();
        }
    }

    public abstract String bar();

    public abstract String foo();
}
Buzz.java
public final class Buzz implements Bar {

    public String bar() {
        return "Buzz";
    }

    public Buzz() {
    }

    public String foo() {
        return Bar.DefaultImpls.foo(this);
    }
}

Barで実装したはずの、foo()は内部クラスのDefaultImplsにちょっと形を変えて潜り込ませて、foo()自体は、abstractのまま残しています。そして、Buzzでfoo()を実装して、その中で、潜り込ませたfoo()を呼び出しています。

第1引数でthisを渡すあたり、pythonを彷彿とさせますが、まぁ、C++なんかでも使われているオブジェクトのメソッドを(メソッドではないという意味の)関数で実現するオーソドックスな方法です。ちなみに、このパターンは、拡張関数でも使用されています。

まとめ

  • interfaceでの実装は、内部クラスのstaticなメソッドとして隠蔽される
  • 継承元の仮想メソッドは仮想のまま引き継がれる
  • interfaceを実装するクラスで、仮想メソッドが実装され、その際に、interfaceの内部クラスのメソッドが呼び出される

ということですね。

蛇足

Kotlinの仕組みについて理解が深まったのはいいのですが、発端になった問題は解決していません。

MainActivityに、PermissionHelperの内部クラスを使った形で、onRequestPermissionsResult()メソッドができないのは、おかしいんじゃないの?
と、頭を抱えていたのですが、エラーメッセージをよく見てみると、

because it inherits many implementations of it

と言う文字列が。

onRequestPermissionsResult()は、Fragmentからの呼び出しランタイムパーミッションの処理を呼び出したときのために、FragmentActivityで、既に実装されていたのでした(サポートライブラリの闇は深いね)。

なので、

MainActivity.kt
class MainActivity : AppCompatActivity(), PermissionHelper {

    :

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super<PermissionHelper>.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
}

を追加してやれば、コンパイル可能になります。
だけど、これじゃ当初の目的(ボイラープレートなコードをなくす)は果たしてないんだよね。 orz