1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlin の companion object の protected メソッドをコールすると実行時例外が発生する対処策

Posted at

概要

表題のとおり、companion object に定義した protected メソッドをコールすると、下記の実行時例外が発生しました。

java.lang.IllegalAccessError: tried to access method com.sample.Parent$Companion.parentStaticProtected()Ljava/lang/String; from class com.Child$Companion

	at com.Child$Companion.parentFromChild(Child.kt:10)
	at another.ChildTest.call_test(ChildTest.kt:10)

詳細

ソースコードの構成は下記のようになっています。

.
└── com
    ├── Child.kt
    └── sample
        ├── Parent.kt

Child クラスから、Parent クラスをコールします。

Child.kt
package com

import com.sample.Parent

class Child: Parent() {
    companion object {
        fun parentFromChild(): String {
            return parentStaticProtected()
        }
    }
}
Parent.kt
package com.sample

abstract class Parent {
    companion object {
        @JvmStatic
        protected fun parentStaticProtected(): String {
            return "IllegalAccessError!!"
        }
    }
}

Parent の parentStaticProtected についている @JvmStatic アノテーション は、companion object の protected メソッドには、このアノテーションをつけるようコンパイラがエラーを出すので、コンパイルエラーを解決するためにつけています。

原因

Kotlin には、パッケージプライベートの概念はないのですが、どうやら companion object の protected メソッドは、同一パッケージ内でからのみコールできるようです。
試しに、Child クラスのパッケージを com から、com.sample に変更したところ、正常終了しました。

Child.kt
package com.sample

OptIn アノテーションで回避

パッケージの移動で回避できるものの、2 つの課題を感じました。

  • 同一パッケージ外に companion object に protecte メソッドをもったサブクラスをおきたい時があるだろう
  • 例外の意味がよくわからないので実行時例外前にコンパイルエラーで気付きたい

この挙動を変更してくれればよいのですが、いつになるかがよくわからないので、対処策として、Opt-In Requirements の機能を使って回避してみました。
この方法ですと、後述の @OptIn のアノテーションを、利用したくなったら都度メソッドにつける方法でコールできてしまいます。不適切なスコープからコールされる可能性はあるので、せめてそうならないよう、それらしい名前のアノテーションで定義します。

SubClassCanUse.kt
package com.annotation

/**
 * このアノテーションは、実質 @RequiresOptIn アノテーションですが、
 * 役割を明確にするため別途アノテーション定義しています。
 */
@RequiresOptIn
annotation class SubClassCanUse

公式のページによると、@RequiresOptIn アノテーション をつけたメソッドをコールするために、特殊な制限をかけることができます。

Parent.kt
    companion object {
        @SubClassCanUse
        fun parentStaticProtected(): String {
            return "now we can call this... because scope is public."
        }
    }

このメソッドをコールしようとすると、

This declaration is experimental and its usage must be marked with '@com.annotation.SubClassCanUse' or '@OptIn(com.annotation.SubClassCanUse::class)'

とコンパイルエラーが発生します。コンパイルエラーを解決するためには、メッセージのとおり、呼び出し元に、

  • @SubClassCanUse
  • @OptIn

のどちらかのアノテーションを書く必要あります。@SubClassCanUse アノテーション でコンパイルエラーを解決する場合、parentStaticProtected をコールしているメソッドだけでなく、間接的にコールするメソッドを含めて、parentStaticProtected メソッドに到達するまでに利用されるすべてのメソッドに @SubClassCanUse アノテーション を設定しないといけません。
この方法だと、非常に多くの場所にアノテーションを記述するハメになり、ノイズになってしまうので、@OptIn アノテーション を使用してコンパイルエラーを解決します。このアノテーションでコンパイルエラーを解決する場合は、直接 @SubClassCanUse アノテーションがついたメソッドをコールしているメソッドにだけ記述すれば良いです。

Child.kt

package com

import com.annotation.SubClassCanUse
import com.sample.Parent

class Child: Parent() {
    companion object {

        @OptIn(SubClassCanUse::class)
        fun parentFromChild(): String {
            return parentStaticProtected()
        }
    }
}
1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?