kotlin内联函数

今天的主角是 inline ,这不是一个 Kotlin 特有的概念,大多数编程语言都支持内联。

内联函数 的语义很简单:把函数体复制粘贴到函数调用处 。使用起来也毫无困难,用 inline关键字修饰函数即可。

然而问题的关键并不是如何使用 inline ,而是什么时候使用  inline ?  既然 Kotlin 提供了内联,它肯定是为了性能优化而存在的,那么,它又真的是包治百病的性能良药吗?

今天,我们就一起来刨根挖底,寻找一下答案。

目录

  1. inline 的本质

  2. 建议我不要用 inline ?

  3. Java 支持内联吗?

  4. 拯救 Lambda

  5. Java 是如何优化 Lambda 的?

  6. 不想内联怎么办?

  7. 如何从 Lambda 返回?

  8. 最后

inline 的本质

前面已经说过  inline 就是 把函数体复制粘贴到函数调用处 ,完全是编译器的小把戏。本着严谨科学的态度,我们还是来反编译验证一下。

inline fun test() {
   
   println("I'm a inline function")
}

fun run() {
    test() }

run() 函数中调用了内联函数 test()。反编译查看对应的 java 代码:

  public static final void test() {
   
    String var1 = "I'm a inline function";
    System.out.println(var1);
}
 
public static final void run() {
   
    String var1 = "I'm a inline function";
    System.out.println(var1);
}

可以看到 run() 函数中并没有直接调用 test() 函数,而是把 test() 函数的代码直接放入自己的函数体中。这就是 inline 的功效。

那么,问题就来了。这样就可以提高运行效率吗?

今天的主角是 type alias,翻译过来叫 类型别名。先来看一下文章目录:

  1. 什么是 typealias ?

  2. typealias 的本质

  3. typealias 存在的意义是什么?

  4. typealias 的使用注意事项

什么是 typealias ?

这是一个很基础的关键字,但可能很多人没有使用过。它的作用十分简单,给已有类型取一个别名,可以像使用原类型一样使用这个 “类型别名”

举个简单的例子:


 
 
 
   
   
   
   
  1. typealias Name = String
  2. val name : Name =  "java"
  3. println(name.length)

String 取个别名 Name ,在使用过程中,NameString 是完全等价的。

既然是等价的,使用别名的意义是什么呢?

别急,typealias 不仅仅支持给类取别名,它的用法丰富的让你想象不到。

// 类和接口
typealias Name = String
typealias Data = Serializable

// 函数类型
typealias GetName = () -> String
typealias Handler = CoroutineScope.() -> Unit

// 泛型
typealias P<T> = Comparable<T>
typealias Pairs<K, V> = HashMap<K, V>
typealias Numbers = Array<Number>

// object
object Single {
   }
typealias X = Single

class Util {
   
   companion object {
   
       val count = 1
   }
}
typealias Count = Util.Companion

// inner class
typealias NotificationBuilder = NotificationCompat.Builder

class Outer {
    inner class Inner }
typealias In = Outer.Inner

// 枚举
enum class Color {
    RED, YELLOW, GREEN }
typealias color = Color

// 注解
typealias Jvm = JvmStatic

上面的枚举用法中,需要注意的一点是,只能为枚举类 Color 取别名,无法为具体的枚举值取别名 。诸如 typealias Red = Color.RED 是不允许的。

几乎没有 typealias 不适用的类型。说到现在,你又得疑问了,类型别名存在的意义是什么 ?这样简单的取个别名,为什么不直接使用原类型呢 ?

typealias 的本质

暂且别急,我们先来看一下 typealias 的实现原理,说不定可以有一些发现。

反编译下面这个简单的例子:


 
 
 
   
   
   
   
  1. typealias Binary = ByteArray
  2. fun getBinary( string: String) : Binary =  string.toByteArray()

查看其 Java 代码 :

public final class TypealiasKt {
   
  @NotNull
  public static final byte[] getBinary(@NotNull String string) {
   
     Intrinsics.checkParameterIsNotNull(string, "string");
     Charset var2 = Charsets.UTF_8;
     boolean var3 = false;
     byte[] var10000 = string.getBytes(var2);
     Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).getBytes(charset)");
     return var10000;
  }
}

代码中根本找不到类型别名 Binary 的踪影。经过编译之后,类型别名会被原类型直接替换。这仅仅只是 Kotlin 丰富的语法糖之一,编译器在其中做了一些手脚。

typealias 存在的意义是什么 ?

现在,你估计更加困惑了。

开发者手动声明一个类型别名,编译器再自动替换回原类型。意义何在?

唯一能想到的一点大概只有 "代码可读性" ,这里的代码可读性还要打上了一个大大的引号。

复杂的业务逻辑下,你的代码中可能会出现超长命名,多参数,多泛型类型的类名,接口名,函数名。


 
 
 
   
   
   
   
  1. typealias FileTable = MutableMap>
  2. typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
  3. typealias SimpleName = LonglonglonglonglonglonglonglonglonglonglonglonglonglonglongName

用类型别名来代替原本可读性不好(名字太长或者声明复杂)的类型名,这可能就是 typealias 的主要作用。

至于到底有没有提升可读性?我觉得这是有代价的。因此而丧失的是直观的类型声明。以上面代码块中的 FileTable 为例,一眼看过去,你能发现它是一个 MutableMap> 吗?肯定不能。特别在团队开发中,除了这个代码的贡献者,可能每一位都要到类型别名声明处进行查看。

有人可能也会有不一样的声音。统一的全局声明很正常,而且也方便做统一修改,避免到代码使用处一一修改。况且 IDE 都会自动提示类型别名的声明。没有不使用 typealias 的道理。

所以,这是一个仁者见仁,智者见智的问题。你觉得有用就可以使用,任何一门技术肯定都有它的使用场景,并没有必要去过多争论。

我用协程还是用 RxJava?我用 LiveData 还是用事件总线?我用 ViewBinding 还是 DataBinding ?......

这几个问题可能比较常见,但是上面的每一组选择,如果你真正深入了解其技术本质的话,就会发现它们的使用场景并不一样,也就不会存在 如何选择 的疑问了。

typealias 使用注意事项

有点跑偏了,再回到 typealias 。后面再说一些 typealias 的注意事项,内容会比较零散,后续也可能继续增加。

typealias 可以写在哪里?

只能声明在文件顶层位置,其他任何地方都不行。

你可能感兴趣的:(kotlin,inline,noinline,内联函数)