首页 > 解决方案 > 父类中受保护的内联方法不能访问其他受保护的方法

问题描述

对于以下示例,我在获取IllegalAccessError时遇到问题:

我在名为的 gradle 模块中声明了一个基类arch

abstract class BaseClass {
    protected abstract val value: Int

    fun run() {
        Log.d("Printme", "value $value")
    }

    protected inline fun getMyValue(): Lazy<Int> = lazy {
        getAnEight()
    }

    protected fun getAnEight() = 8
}

和在 gradle 模块中声明的子类称为app

class ChildClass: BaseClass() {
    override val value by getMyValue()
}

值得一提的是,我正在使用 Android Studio 创建一个 Kotlin 项目,但是这些类都是简单的 Kotlin 对象,没有任何 Android 特定的引用。当然这两个模块也有不同的包。

现在,从我的主要输入方法中,我正在执行以下操作(app模块内部)

ChildClass().run()

我正在调用run()在基类中声明的方法,该方法正在访问延迟启动的value属性,而该属性又是调用getAnEight()方法。由于所有方法都受到保护,我希望子类没有理由不能调用所有这些方法。即使其中一个方法被标记为inline并且此调用被方法内容替换,它仍然应该能够正常调用getAnEight()

相反,我收到IllegalAccessErrorBaseClass.getAnEight() is inaccessible to class ChildClass$$special$$inlined$getMeValue$1。当我删除inline修饰符时,这个问题就会消失,或者如果我BaseClassChildClass.

这是 Kotlin 编译器中的错误吗?或者如果它按预期工作,有人可以向我解释这种行为吗?提前致谢!

标签: kotlininlineaccess-modifiers

解决方案


https://kotlinlang.org/docs/reference/inline-functions.html#public-inline-restrictions

当内联函数是公共的或受保护的并且不是私有或内部声明的一部分时,它被视为模块的公共 API。它可以在其他模块中调用,也可以在此类调用站点内联。

这会带来某些由声明内联函数的模块更改引起的二进制不兼容风险,以防调用模块在更改后未重新编译。

为了消除由于模块的非公共 API 更改而引入这种不兼容的风险,公共 API 内联函数不允许在其主体中使用非公共 API 声明,即私有和内部声明及其部分.

内部声明可以使用 @PublishedApi 进行注释,这允许它在公共 API 内联函数中使用。当内部内联函数被标记为@PublishedApi 时,它的主体也会被检查,就好像它是公共的一样。

编辑:我做了一些字节码研究。问题是受保护的 getMyValue() 函数被内联到公共构造函数中。在反编译的字节码中,ChildClass 公共构造函数有以下一行:

Lazy var4 = LazyKt.lazy((Function0)(new ChildClass$$special$$inlined$getMyValue$1(this)));

如您所见,它创建了一个 class 实例ChildClass$$special$$inlined$getMyValue$1。让我们看看它的声明:

public final class ChildClass$$special$$inlined$getMyValue$1 extends Lambda implements Function0 {

    final BaseClass this$0;

    public ChildClass$$special$$inlined$getMyValue$1(BaseClass var1) {
        super(0);
        this.this$0 = var1;
    }

    public Object invoke() {
        return this.invoke();
    }

    public final int invoke() {
        return this.this$0.getAnEight(); // Here lies the problem
    }
}

创建 ChildClass 实例时,其构造函数仅创建一个ChildClass$$special$$inlined$getMyValue$1实例,不会引发任何错误。但是当你调用 run() 时,会调用invoke()上面类的方法。这个方法是公共的,它的类是公共的,构造函数是公共的,但是 getAnEight 方法是受保护的。这就是我们得到这个错误的方式。


推荐阅读