Kotlin原理
package Day03
fun main(){
/*Kotlin 的编译流程*/
//Kotlin 的代码在运行之前,要先经过编译(Compile)
println("Hello world.")
//经过编译以后,它会变成类似这样的东西
/* Java 的字节码,专门给 JVM 执行的
LDC "Hello world."
INVOKESTATIC kotlin/io/ConsoleKt.println (Ljava/lang/Object;)V
*/
//Java 和 Kotlin 本质上是在用同一种语言进行沟通
//Kotlin 和 Java 用的什么语言-----它们用的就是 Java 字节码
/*如何研究 Kotlin?*/
//直接研究 Kotlin 编译后的字节码(明显吃力不讨好)
//将 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 里到底有没有“原始类型”?*/
// 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 代码
// 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;
/*
·对于变量 a、c 来说,它们两个的类型是不可为空的,所以无论如何都不能为 null,
对于这种情况,Kotlin 编译器会直接将它们优化成原始类型。
*
·对于变量 b、d 来说,它们两个的类型虽然是可能为空的,但是它的值不为 null,
并且,编译器对上下文分析后发现,这两个变量也没有在别的地方被修改。
这种情况,Kotlin 编译器也会将它们优化成原始类型。
*
·对于变量 e、f、g 来说,不论它们是 val 还是 var,只要它们被赋值过 null,
那么,Kotlin 就无法对它们进行优化了。这背后的原因也很简单,
Java 的原始类型不是对象,只有对象才能被赋值为 null。
*/
//Kotlin 对基础类型的转换规则:
//只要基础类型的变量可能为空,那么这个变量就会被转换成 Java 的包装类型。
//反之,只要基础类型的变量不可能为空,那么这个变量就会被转换成 Java 的原始类型。
}
/*接口语法的局限性*/
// Kotlin 代码
interface Behavior {
// 接口内可以有成员属性
val canWalk : Boolean
// 接口方法的默认实现
fun walk() {
if (canWalk) {
println(canWalk)
}
}
}
// 等价的 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);
}
}
}
}
*/
class Man : Behavior{
override val canWalk: Boolean = true
}
// 等价的 Java 代码
/*
public final class Man implements Behavior {
private final boolean canWalk = true;
public boolean getCanWalk() {
// 关键点 ①
return this.canWalk;
}
public void walk() {
// 关键点 ②
Behavior.DefaultImpls.walk(this);
}
}
*/
private fun testInterface() {
val man = Man()
man.walk()
}
//图img_2分析
/*
* 箭头①,代表 Kotlin 接口属性,实际上会被当中接口方法来看待。
* 箭头②,代表 Kotlin 接口默认实现,实际上还是一个普通的方法。
* 箭头③,代表 Kotlin 接口默认实现的逻辑是被放在 DefaultImpls 当中的,
它成了静态内部类当中的一个静态方法 DefaultImpls.walk()。
* 箭头④,代表 Kotlin 接口的实现类必须要重写接口当中的属性,同时,它仍然还是一个方法。
* 箭头⑤,即使 Kotlin 里的 Man 类没有实现 walk() 方法,但是从 Java 的角度看,
它仍然存在 walk() 方法,并且,walk() 方法将它的执行流程转交给了 DefaultImpls.walk(),
并将 this 传入了进去。这样,接口默认方法的逻辑就可以成功执行了。
*/
//Kotlin 接口当中的属性,在它被真正实现之前,本质上并不是一个真正的属性。
//因此,Kotlin 接口当中的属性,它既不能真正存储任何状态,也不能被赋予初始值,
//因为它本质上还是一个接口方法。
/*
* Kotlin 代码,最终都会被 Kotlin 编译器进行一次统一的翻译,
* 把它们变成 Java 能理解的格式。Kotlin 的编译器,
* 在这个过程当中就像是一个藏在幕后的翻译官。
*/
//Kotlin 的每一个语法,最终都会被翻译成对应的 Java 字节码。
/*
* 类型推导,我们写 Kotlin 代码的时候省略的变量类型,最终被编译器补充回来了。
* 原始类型,虽然 Kotlin 没有原始类型,
但编译器会根据每一个变量的可空性将它们转换成“原始类型”或者“包装类型”。
* 字符串模板,编译器最终会将它们转换成 Java 拼接的形式。
* when 表达式,编译器最终会将它们转换成类似 switch case 的语句。
* 类默认 public,Kotlin 当中被我们省略掉 public,最终会被编译器补充。
* 嵌套类默认 static,我们在 Kotlin 当中的嵌套类,
默认会被添加 static 关键字,将其变成静态内部类,防止不必要的内存泄漏。
* 数据类,Kotlin 当中简单的一行代码“data class Person(val name: String, val age: Int)”,
编译器帮我们自动生成很多方法:getter()、setter()、equals()、hashCode()、
toString()、componentN()、copy()。
*/
思考题
package Day03
//思考题:
//为 Person 类增加 isAdult 属性,我们要通过自定义 getter 来实现
class Person(val name: String, var age: Int) {
val isAdult
get() = age >= 18
}
//而下面这种写法则是错误的:
class Person2(val name: String, var age: Int) {
val isAdult = age >= 18
}
/*
* 思考题分析:
转换成 java代码就一清二楚,两种方式的isAdult本质不是同一个东西:
1-通过自定义 getter 来实现的方式,isAdult其实是一个方法。外部每一次调用,
都是拿最新的age进行计算,所以age的值有变动,isAdult()的结果是最新的。
2-val isAdult = age >= 18 这种方式,isAdault是一个final变量,
只会在对象新建时,在构造方法中,根据age的值赋值一次。所以,之后age的值如果有变动,
isAdault值是永远不变的。
*/