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调用它时,会出现编译错误?
当一个内联函数是 public
或 protected
而不是 private
或 internal
声明的一部分时,就会认为它是一个模块级的公有 API。可以在其他模块中调用它,并且也可以在调用处内联这样的调用。
这带来了一些由模块做这样变更时导致的二进制兼容的风险——声明一个内联函数但调用它的模块在它修改后并没有重新编译。
为了消除这种由非公有 API 变更引入的不兼容的风险,公有 API 内联函数体内不允许使用非公有声明,即,不允许使用 private
与 internal
声明以及其部件。
一个 internal
声明可以由 @PublishedApi
标注,这会允许它在公有 API 内联函数中使用。当一个 internal
内联函数标记有 @PublishedApi
时,也会像公有函数一样检测其函数体。
网友评论