本课程为极客时间上朱涛老师的《Kotlin编程第一课》的学习笔记
1.Kotlin的编译流程
JavaCoder应该都明白,Java之所以能跨平台运行,是在于JVM接受Java编译后的字节文件;所以Kotlin同样是基于JVM的语言,它又是如何做到能兼容Java的同时,还有自己的一些特性;而它又是怎么在JVM上运行的?
这里打个比方
println("Hello World.")
这样的语言JVM是无法直接执行了;经过编译后,它对应的文件格式如下
LDC "Hello world."
INVOKESTATIC kotlin/io/ConsoleKt.println (Ljava/lang/Object;)V
聪明的你会发现这不就是Java的字节码,对的,Kotlin最终的编译结果还是字节码;所以总的来说:Java和Kotlin本质是用同一种语言在沟通
下面是就是Java和Kotlin的编译流程
2.如何分析Kotlin代码
因为大部分研发对于Java的了解度很高,可以从Java的角度去分析Kotlin的代码;那么是否可以按照下面这个流程来分析:
- 首先把kotlin的代码编译成字节码
- 再将编译后的字节码转换为Java代码
- 等价分析Kotlin和Java的代码
下面就是这样流程的变化过程
println("Hello world.") /*
编译
↓ */
LDC "Hello world."
INVOKESTATIC kotlin/io/ConsoleKt.println (Ljava/lang/Object;)V /*
反编译
↓ */
String var0 = "Hello world.";
System.out.println(var0);
可以理解Kotlin中的println等价于Java中的System.out.println;
那么有什么工具可以实现上面的需求?
还好,这样的需求,Android Studio已经提供工具;具体的操作如下:
第一步,打开我们需要分析的Kotlin代码
第二步,依次点击菜单栏:Tools -> Kotlin -> Show Kotlin Bytecode;这里能看到编译后的字节码
第三步,点击画面右边的“Decompile”按钮;
就可以看到反编译后的Java代码了
3.Kotlin是否有基础类型
实例1:Kotlin中的Long是否和Java中的long/Long有关系?
这里就用上面的思路来分析,首先上Kotlin源码
// kotlin 代码
// 用 val 定义可为空、不可为空的Long,并且赋值
val a: Long = 1L
val b: Long? = 2L
// 用 var 定义可为空、不可为空的Long,并且赋值
var c: Long = 3L
var d: Long? = 4L
// 用 var 定义可为空的Long,先赋值,然后改为null
var e: Long? = 5L
e = null
// 用 val 定义可为空的Long,直接赋值null
val f: Long? = null
// 用 var 定义可为空的Long,先赋值null,然后赋值数字
var g: Long? = null
g = 6L
最终对应的Java代码如下
// 反编译后的 Java 代码
long a = 1L;
long b = 2L;
long c = 3L;
long d = 4L;
Long e = 5L;
e = (Long)null;
Long f = (Long)null;
Long g = (Long)null;
g = 6L;
最终分析结果如下:
-
基础类型的变量可能为空,那么这个变量就会被转换成 Java 的包装类型
-
基础类型的变量不可能为空,那么这个变量就会被转换成 Java 的原始类型
这样的方式很好的利用了基础类型占用低的特性,提高了特性
实例2:分析Kotlin中Interface的成员属性
备注:Kotlin中的成员属性是如何实现的?Java对应的方法?
// Kotlin 代码
interface Behavior {
// 接口内可以有成员属性
val canWalk: Boolean
// 接口方法的默认实现
fun walk() {
if (canWalk) {
println(canWalk)
}
}
}
private fun testInterface() {
val man = Man()
man.walk()
}
转换后对应的Java代码
// 等价的 Java 代码
public interface Behavior {
// 接口属性变成了方法
boolean getCanWalk();
// 方法默认实现消失了
void walk();
// 多了一个静态内部类
public static final class DefaultImpls {
public static void walk(Behavior $this) {
if ($this.getCanWalk()) {
boolean var1 = $this.getCanWalk();
System.out.println(var1);
}
}
}
}
Kotlin 接口的“默认属性”canWalk,本质上并不是一个真正的属性,当它转换成 Java 以后,就变成了一个普通的接口方法 getCanWalk()。
这里补充上Man的代码
class Man:Behavior {
override val canWalk: Boolean
get() = true
}
对应的Java代码
public final class Man implements Behavior {
public boolean getCanWalk() {
return true;
}
public void walk() {
DefaultImpls.walk(this);
}
}
总结:Kotlin 的新特性,最终被转换成了一种 Java 能认识的语法
4.小结
所有的Kotlin代码,最终都会被Kotlin编译器进行一次统一的翻译,把它们变成Java能理解的格式。
5.思考题
之前提到的问题,为Person类增加isAdult属性,我们要通过自定义getter来实现,如:
class Person(val name: String, var age: Int) {
val isAdult
get() = age >= 18
}
而下面这种写法则是错误的:
class Person(val name: String, var age: Int) {
val isAdult = age >= 18
}
为什么?
前一个代码转换成Java代码对应是:
public final boolean isAdult() {
return this.age >= 18;
}
错误代码转换成Java对应的代码是:
public final class Person {
private final boolean isAdult;
public final boolean isAdult() {
return this.isAdult;
}
public Person(@NotNull String name, int age) {
...
super();
this.name = name;
this.age = age;
this.isAdult = this.age >= 18;
}
}
它的问题就在于isAdult在初始化以后就不会变化了;后面age发生变化了,它也不会更新;