美文网首页
inline function cannot access no

inline function cannot access no

作者: kevinsEegets | 来源:发表于2020-07-15 14:39 被阅读0次

https://stackoverflow.com/questions/41892715/inline-function-cannot-access-non-public-api-publishedapi-vs-suppress-vs-jvm

https://juejin.im/entry/5d1c974e6fb9a07eff00a477

在kotlin中,当我有一个非public成员变量或方法并使用inline fun调用它时,会出现编译错误:

inline function cannot access non-public-API: @PublishedApi vs @Suppress vs @JvmSynthetic

我们首先了解一下什么是inline函数? 为什么要提供这个函数?

什么是inline内联函数?

内联函数其实是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段

我们知道Java是没有内联这个概念的,所有的函数调用都是普通方法调用,如果了解Java虚拟机原理的,可以知道Java方法执行的内存模型是基于Java虚拟机栈的:每个方法被执行的时候都是会创建一个栈(Stack Frame),用于存储局部变量,操作数栈,动态链接,方法等信息。 每一个方法被调用直到执行完成的过程,,就对应一个出栈和入栈操作过程.

也就是说每调用一个方法,都会对应一个栈帧的入栈出栈过程,如果你有一个工具类方法,在某个循环里调用很多次,那就会对应很多次的栈帧入栈、出栈过程。

为什么要提供inline内联函数?

我们用一个例子说明一下:

fun test() {
    //多次调用 sum() 方法进行求和运算
    println(sum(1, 2, 3))
    println(sum(100, 200, 300))
    println(sum(12, 34))
    //....可能还有若干次
}

/**
 * 求和计算
  */
fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

我们发编译上述Kotlin代码,得到Java代码:

 public final void test() {
      int var1 = this.sum(1, 2, 3);
      ...
      var1 = this.sum(100, 200, 300);
      ...
      var1 = this.sum(12, 34);
   }

   public final int sum(@NotNull int... ints) {
      Intrinsics.checkParameterIsNotNull(ints, "ints");
      int sum = 0;
      int[] var5 = ints;
      int var6 = ints.length;

      for(int var4 = 0; var4 < var6; ++var4) {
         int i = var5[var4];
         sum += i;
      }

      return sum;
   }

通过查看反编译的Java代码,可以看到测试方法test()里,我们多次调用sum()方法,为了避免多次调用sum()方法带来的性能损耗,我们期望的代码类似这个样子:

fun test() {
    var sum = 0
    for (i in arrayOf(1, 2, 3)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(100, 200, 300)) {
        sum += i            
    }
    println(sum)
    
    sum = 0
    for (i in arrayOf(12, 34)) {
        sum += i            
    }
    println(sum)
}

解释一下:3次数据求和操作,都是在test()方法里执行的,没有了之前的sum()方法调用,最后的结果依然是一样的,但是由于减少了方法调用,虽然代码量增加了,但是性能确提升了,内联函数就是为了解决这一问题。

用内联的方式修改如上代码:

inline fun sum(vararg ints: Int): Int {
    var sum = 0
    for (i in ints) {
        sum += i
    }
    return sum
}

我们再次反编译带inline的Kotlin代码,得到Java代码:

public final void test() {
      ...
      for(var7 = 0; var7 < var6; ++var7) {
         i$iv = var5[var7];
         sum$iv += i$iv;
      }
      ...
      for(var7 = 0; var7 < var6; ++var7) {
         i$iv = var5[var7];
         sum$iv += i$iv;
      }
      ...
      for(var7 = 0; var7 < var6; ++var7) {
         i$iv = var5[var7];
         sum$iv += i$iv;
      }
   }

   public final int sum(@NotNull int... ints) {
      int $i$f$sum = 0;
      Intrinsics.checkParameterIsNotNull(ints, "ints");
      int sum = 0;
      int[] var6 = ints;
      int var7 = ints.length;

      for(int var5 = 0; var5 < var7; ++var5) {
         int i = var6[var5];
         sum += i;
      }

      return sum;
   }

通过反编译Java代码可以看到:test() 方法里已经没了对 sum() 方法的调用,凡是原来代码里出现 sum() 方法调用的地方,出现的都是 sum() 方法体内的字节码了。

现在回到我们一开始的问题,为什么当我有一个非public成员变量或方法并使用inline fun调用它时,会出现编译错误?

当一个内联函数是 publicprotected 而不是 privateinternal 声明的一部分时,就会认为它是一个模块级的公有 API。可以在其他模块中调用它,并且也可以在调用处内联这样的调用。

这带来了一些由模块做这样变更时导致的二进制兼容的风险——声明一个内联函数但调用它的模块在它修改后并没有重新编译。
为了消除这种由公有 API 变更引入的不兼容的风险,公有 API 内联函数体内不允许使用非公有声明,即,不允许使用 privateinternal 声明以及其部件。

一个 internal 声明可以由 @PublishedApi 标注,这会允许它在公有 API 内联函数中使用。当一个 internal 内联函数标记有 @PublishedApi 时,也会像公有函数一样检测其函数体。

相关文章

网友评论

      本文标题:inline function cannot access no

      本文链接:https://www.haomeiwen.com/subject/bhcdhktx.html