从内联函数到泛型数组:几个 Kotlin 高级特性的坑(上)

从内联函数到泛型数组:几个 Kotlin 高级特性的坑(上)

今天尝试写 Kotlin 的 ArrayList,一开始就出现了兼容性问题,拿出来和大家分享一下心得。

内联函数 inline fun

首先要说到内联函数这个东西。维基百科上的解释是:

在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。另外还需要非常注意的是对递归函数的内联扩展可能带来部分编译器的无穷编译。

Kotlin 类库中定义了大量的内联函数,我们之前讲到的很多高阶函数都是内联函数。比如常用的 repeat() 函数,它的定义如下:

inline fun repeat(times: Int, action: (Int) -> Unit) {
    for (index in 0..times - 1) {
        action(index)
    }
}

所有对 repeat() 函数的调用都会被编译为一个 for 循环语句,比如下面的调用:

repeat(10) {
  println(it)
}

就会被编译为下面的样子:

for(index in 0..9) {
  println(index)
}

不过,Kotlin 并不建议将非高阶函数定义为内联函数:

inline fun foo() = println("foo")

编译,Kotlin 编译器会给我们一个警告:

Warning:(1, 1) 
Kotlin: Expected performance impact of inlining 'public inline fun foo(): Unit defined in root package' can be insignificant. 
Inlining works best for functions with lambda parameters

编译器告诉我们,内联最好与有 Lambda 表达式参数的函数配合使用,内联这里的 foo() 函数对性能的影响微乎其微。

reified 修饰符

这个修饰符是我们今天讲的重点。"reified" 是“具体的”的意思,Kotlin 中这个关键字只用来修饰 泛型内联函数的类型参数,它要求这个内联函数在展开到调用时,必须用一个具体的类型来替换类型参数。它主要用来替代不必要的 Class<T> 传参,在一定程度上简化代码、提高运行速度。

比如下面的函数:

inline fun <reified T> listMethodsOf()
        = T::class.java.methods.toList()

它的作用是获取 T 类型的所有方法并转换为 List,如果在 Java 中写,我们必须给它传一个 Class 类型的参数,就像这样:

public static List<Method> listMethodsOf(Class classIn) {
  return Arrays.asList(classIn.getMethods());
}

对于这个 Kotlin 方法,我们可以这样调用它:

println(listMethodsOf<String>())

而要调用 Java 方法,我们必须传给它一个 Class 类型参数:

System.out.println(listMethodsOf(String.class))

通过 inline - reified 组合使用,我们可以避免传递没有必要的 Class 参数,使用更加简单易懂的泛型参数来调用函数。

不过,这里也有两个大坑,我们下节会仔细讲:

  • reified 关键字要求调用时必须使用具体的类型,不能使用泛型

  • 使用了 reified 关键字修饰的内联函数,不能在 Java 中调用

几个与内联有关的关键字和注解

首先是 noinline,它用来修饰高阶函数的参数类型参数,告诉编译器不要将这个函数内联化:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {/*...*/}

这个 foo() 函数用 inline 声明为内联函数,接受两个函数作为参数。因为第二个函数参数用 noinline 修饰,Kotlin 编译器在调用 foo() 函数处就只会将 inlined 函数内联化,而不会内联化 notInlined 函数。

其次是 kotlin.internal.@InlineOnly,用它修饰的内联函数只能以内联的形式访问,比如上面举到的 repeat() 函数,就用到了这个注解:

@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    for (index in 0..times - 1) {
        action(index)
    }
}

需要注意,这个注解的访问控制符是 internal,只供 Kotlin 类库使用,我们只需要知道它有什么用就行了。

发布于 2017-06-06 14:24