一文彻底搞懂kotlin inline

2021-01-12 14:49:24 浏览数 (1)

小憩第55篇原创文章

Kotlin语言相信大家已经玩的很溜了,但大家有没有注意到它内部源码大量使用了inline,那么Kotlin为什么要使用inline?它的作用又是什么呢?

如果你只是注意到了,但从来没有进行深入探究,相信这篇文章能够帮你找到答案。

inline

inline是作用在函数方法上面的,例如Kotlinlet方法

代码语言:javascript复制
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

那它的作用是什么呢?

inline主要是对闭包block做优化,为了对比它做的优化,我对应定义一个没有inline的方法

代码语言:javascript复制
public fun <T, R> T.ret(block: (T) -> R): R {
    return block(this)
}

然后我同时调用这两个方法

代码语言:javascript复制
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        let {
            it.a()
        }

        ret {
            it.b()
        }

    }

    fun a() {

    }

    fun b() {

    }

    fun <T, R> T.ret(block: (T) -> R): R {
        return block(this)
    }
}

再通过ASShow Kotlin Bytecode,来看它们反编译的二进制代码

代码语言:javascript复制
public final class MainActivity extends AppCompatActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300009);
      boolean var3 = false;
      boolean var4 = false;
      // inline修饰的let
      MainActivity it = (MainActivity)this;
      int var6 = false;
      it.a();
      // 没有inline修饰的ret
      this.ret(this, (Function1)null.INSTANCE);
   }

   public final void a() {
   }

   public final void b() {
   }

   public final Object ret(Object $this$ret, @NotNull Function1 block) {
      Intrinsics.checkParameterIsNotNull(block, "block");
      return block.invoke($this$ret);
   }

   ...
}

不懂的还是要看源码,程序员的终结武器

在这里我们发现通过inline修饰的方法,会通过平坦式的方式直接在后面按执行顺依次调用。

而没有使用inline修饰的方法,则会为block方法创建一个Function1实例。

简单的理解就是未使用inline修饰的方式,会对带有函数式参数的方法,创建对于函数的实例,再将这个实例传递到方法参数中。该参数方法最终在原方法的内部被显示调用。

所以inline做的优化就是将带有函数参数的方法简化成没函数式参数的直接调用。好处是提高程序的性能。

当然需要注意的是,避免使用inline内联大型函数,减少方法中代码的增长。

非局部返回

inline还有一个好处是,对于whilefor等语句,被inline修饰的函数支持局部返回

还是上面的例子

代码语言:javascript复制
while (--i > 0) {
    let {
        return // success
    }
}
 
while (--i > 0) {
    ret {
        return // error: return is not allow here
    }
}

简单的理解就是,使用inline修饰的函数,可以直接在循环语句中通过return跳出循环体。而非inline函数是不支持的,它支持跳出方法体。

原因也很简单,回头再看之前的反编译的二进制代码,因为使用inline修饰的方法是平铺式直接按顺序调用,并没有包含在方法体中,所以如果return的话就相当于直接在循环体中return

代码语言:javascript复制
while(--i > 0) {
 return
 ...
}

而未使用inline修饰的方法,是在另外的方法体中进行调用,所以它的return只能是返回到方法体。

reified

使用inline修饰的函数还有一个好处是可以使用reified来修饰函数的泛型,让函数的泛型具体化

代码语言:javascript复制
inline fun <reified T, R> T.det(block: (T) -> R): R {
    val a = 0
    if (a is T) { // success

    }
    return block(this)
}

fun <T, R> T.ret(block: (T) -> R): R {
    val a = 0
    if (a is T) { // error: Cannot check for instance of erased type: T

    }
    return block(this)
}
 
// 或者
inline fun <reified T> membersOf() = T::class.members

传统的泛型是会在程序运行的过程中进行擦除操作,而使用reified修饰的泛型,通过反编译二进制表现就是将泛型替换成具体的类型,不进行类型擦除。

代码语言:javascript复制
$i$f$det = false;
int a$iv = 0;
if (Integer.valueOf(a$iv) instanceof MainActivity) {
   this.a();
}
 
MainActivity it = (MainActivity)this;
int var7 = false;
it.b();

noinline

有了inline自然也有对立的noinline

对于多个函数方法参数,可以使用noinline来指定某个函数方法参数不使用inline的特性

代码语言:javascript复制
inline fun <T, R> T.net(block: (T) -> R, noinline noBlock: () -> Unit): R {
    noBlock()
    return block(this)
}

这样就只有block会执行inline的优化。

crossinline

还有一种情况,如果使用了inline修饰的函数,被使用到了嵌套的内联函数中,直接使用是会报错的,需要为函数参数添加crossinline修饰符

代码语言:javascript复制
inline fun <T, R> T.cet(block: (T) -> R, crossinline noBlock: () -> Unit): R {
    Runnable {
        noBlock()
    }
    return block(this)
}

今天有关kotlininline部分就分析到这,希望你有所收获。

推荐阅读

数组:面试中的疑难点

Jetpack:DataStore必知的几个优点

Android Startup最新进展

0 人点赞