Kotlin 是如何实现 Java 不存在的特性的?
cfanr Lv4

之前初次接触 Kotlin 时,总有个疑问,同样是运行在 JVM 内,而且 Kotlin 和 Java 可以无缝混合开发,那究竟 Kotlin 是如何实现 Java 不存在的特性的呢? 今天就来粗略了解这些语法糖的奥妙。

Kotlin 支持的 Java 中没有的特性:类型推断、可变性、可空性、自动拆装箱、泛型数组、高阶函数、DSL、顶层函数、扩展函数、内联函数、伴生对象、数据类、密封类、单例类、类代理、internal、泛型具体化……

先说结论,Kotlin 编译器通过以下几种方式支持 Java 没有的特性:
编译器推断、中间代码添加、元注解、Metadata


.kt 是如何编译成 .class 的?

对语言编译过程有基本认识的话,就知道大概分为以下几步:

  • 词法分析:把源码的字符流,转化成标记(Token)序列,标记是语言的最小语义单位,包括关键字、标识符、运算符、常数等;
  • 语法分析:把标记序列,组合成各类语法短句,判断标记序列在语法结构上,是否正确,输出树形结构的抽象语法树;
  • 语义分析:结合上下文,检查每一个语法短句,语义是否正确,是否符合语言规范。

所以 kotlin 支持 Java 不存在的特性,主要还是通过 Kotlin 编译器做了加工处理。


编译器幕后干的好事

以下直接说结论,懒得贴反编译的代码了,可以自己写 kotlin 代码,然后通过 AS 的 Tools/Kotlin/Show kotlin Bytecode,再点击 decompile 查看 Kotlin 转成 Java 的代码

1)类型推断

  • 类型推断并不是不确定数据类型,相反是从上下文推断出一个明确的数据类型;
  • 类型推断的意义在于,去掉代码中的冗余信息,提升研发效率;
  • 类型推断主要发生在语法分析和语义分析阶段
1
2
3
4
5
6
7
8
val name = “Vincent” //String
val x = 1 //int
val y = 1.0f //float
val z = x + y //float
//泛型类型推断
fun <T> create(): T {}
val fruit = create<Apple>()
val apple: Apple = create()

2)Kotlin 的自动拆装箱

  • 自动拆装箱是有性能损耗的
  • 支持泛型类型,所以默认不具备协变性,字节码实现对应anewarray,相当于Java的Integer[]。
  • 字节码实现对应newarray,相当于Java的int[],性能较好。
1
2
3
4
5
6
7
8
9
10
11
12
//Java 1.5 后支持自动拆装箱
Integer x = 1; //自动装箱
int y = x; //自动拆箱

var i: Int = 0 //int
var i: Int? = null //java.lang.Integer
i = 0 //Integer.valueOf

val list = mutableListOf<Int>()

val ids: Array<Int> = arrayof()
val ids : IntArray = IntArrayOf)

总的来说,

  • 可空的基本数据类型,会被编译成装箱类;
  • 泛型中基本数据类型,在使用时,会自动拆装箱;
  • 泛型数组,使用的是装箱类型。
  • 出于性能考虑,为避免自动拆装箱所带来的开销,在Kotlin中,应当尽量避免使用可空的基本数据类型,以及泛型数组;

3)Kotlin的高阶函数(返回函数或参数是函数)

  • 从性能上讲,高阶函数要创建实例,所以开销会增大。
  • Kotlin的匿名内部类,在和外部类有互动的时候,也会持有外部类的引用,存在一定的、潜在的内存泄漏的风险。
  • 高阶函数是通过中间代码添加生成的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class类名$方法名$1 : FunctionReference(receiver:方法所在类),Funtion1 {
override fun invoke(p1: Int) {
receiver.showFailedState(p1)
}
override fun getOwner() : KDeclarationContainer {
return Reflection. getOrCreateKotlinClass(方法所在类.class);
}
override fun getName() : String {
return "showFailedState"
}
override fun getSignature() : String {
return "showFailedState(I)V"
}
})

4)顶层函数

  • Java中的函数,必须在类的内部定义;
  • 而 Kotlin 中允许在类的外部,定义文件级别的顶层函数以及变量。
  • 从字节码层面来说,所有的函数和变量都必须在类的内部;
  • Kotlin编译器,在生成字节码时,会给顶层的函数及变量,创建一个所属的类,类名默认规则是文件名+Kt ;
  • Java代码,可以通过这些Kt结尾的类,调用到这些在Kotlin中定义的顶层函数和变量;
  • 也是通过中间代码添加生成的
1
2
3
4
5
6
7
8
9
10
//TimeUtil.kt  
fun printTime() {
//....
}
// build 后
class TimeUitlKt() {
static void printTime() {
//...
}
}

5)扩展函数

  • 通过中间代码添加生成的
  • 变成类的静态方法,或者是类似顶层函数的逻辑

6)inline 函数

  • 通过中间代码添加生成的
  • inline函数除了能解决泛型具体化问题,还比一般的函数更有性能优势,因为代码的添加发生在编译时,运行时会减少一次虚拟机栈中栈帧的入栈出栈操作;
  • inline函数的副作用是,会导致代码体积增长;

7)其他

  • 数据类 data class -> final class, 生成 getter()、setter()、equals()、hashCode()、toString()、componentN()、copy()
  • 密封类 sealed class -> abstract class + static final inner class
  • 内联类 inline class-> final class, 构造参数变成类的属性
  • 接口里面的默认实现-> 会生成默认实现的类,里面对应实现的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface IFace {
fun func() {
println(“hello”)
}
}

public interface IFace {
void func();
@Metadata(
mv = {1, 7, 0},
k = 3
)
public static final class DefaultImpls {
public static void func(@NotNull IFace $this) {
String var1 = “hello”;
System.out.println(var1);
}
}
}
  • **类型的可空性检查 ->**编译的时候会补充上 NotNull 或 NullAble

  • 泛型的可空检查-> 通过 Kotlin 的 Metadata
    val names = listOf<String?>(“Vincent”, null)

  • 字符串模板 -> 编译器最终会将它们转换成 Java 拼接的形式。

  • when 表达式 -> 编译器最终会将它们转换成类似 switch case 的语句。

  • 类默认 public -> Kotlin 当中被我们省略掉 public,最终会被编译器补充。

  • 嵌套类默认 static -> 我们在 Kotlin 当中的嵌套类,默认会被添加 static 关键字,将其变成静态内部类,防止不必要的内存泄漏。

  • lateinit -> lateinit 用于变量 var,只是让编译期间忽略对属性未初始化的检查,后续在哪里什么时候初始化由开发者决定


suspend 的原理

  • 挂起函数的本质,就是 Callback
  • 这个“从挂起函数转换成 CallBack 函数”的过程,被叫做是 CPS 转换(Continuation-Passing-Style Transformation)
  • 反编译转成 Java 后,可以看到suspend 函数的参数多了一个 Continuation 的类(里面有协程的上下文,和 resumeWith方法),内部是通过 label 来实现程序的 goto 功能,从而实现“状态机”的效果 (详细代码分析略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
suspend fun getUserInfo(): String {
withContext(Dispatchers.IO) {
delay(1000L)
}
return “cfanr”
}

// Continuation 等价于 CallBack
// ↓
public static final Object getUserInfo(Continuation $completion) {

return “cfanr”;
}

public interface Continuation<in T> {
public val context: CoroutineContext
// 相当于 CallBack的onSuccess 结果
// ↓ ↓
public fun resumeWith(result: Result<T>)
}

—End—