Kotlin VS JAVA
- 变量与常量
//java
String name = "Test";
final String name = "Test";
//kotlin
var name = "Test"
- 空判断
//java
if (text != null) {
int length = text.length();
}
//kotlin
val length = text?.length()
- case 语句
//java
int score = // some score;
String grade;
switch (score) {
case 9:
grade = "Excellent";
break;
case 6:
grade = "Good";
break;
case 5:
case 4:
grade = "OK";
break;
case 1:
grade = "Fail";
break;
default:
grade = "Fail";
}
// Kotlin
var score = // some score
var grade = when (score) {
9, 10 -> "Excellent"
in 6..8 -> "Good"
4, 5 -> "OK"
in 1..3 -> "Fail"
else -> "Fail"
}
- 方法定义
// java
int getScore() {
// logic here
return score;
}
// Kotlin
fun getScore(): Int {
// logic here
return score
}
- 类,和接口的继承
// java
public class Child extends Parent implements IHome {
//
}
// kotlin
class Child : Parent(), IHome {
//
}
- java协程和kotlin线程
// java 线程
void threadFun(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
thread.start();
}
// kotlin 协程
fun coroutineFun() {
GlobalScope.launch(Dispatchers.Main) {
//delay(8000L)
println("CoroutineTest World!")
}
}
Java VS Kotlin 生成 Bytecode
Java Kotlin编译过程对比
image.png生成的Bytecode方式
- *.java code 由Javac 生成Bytecode
- *.kt code由 kotlinc-jvm
生成的 Bytecode 比对
Kotlin code 反编译回Java code的 汇编字节码对比。
- 如下Kotlin code Main.kt
class Main {
fun main() {
val m = "Hello"
}
}
- 选择菜单 Tools-> Kotlin-> show Kotlin Bytecode(在Kotlin Bytecode窗口右上方点击 Decompile)
反编译后的code 如下
import kotlin.Metadata;
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/yy/kotlindemo/kotlinbasic/Main;", "", "()V", "main", "", "app_debug"}
)
public final class Main {
public final void main() {
String m = "Hello";
}
}
3.然后分别查看上述两段code的字节码
public class Main {
public void main() {
String m = "Hello";
}
}
-
通过Java Output字节码汇编比对, kotlin使用kotlin 1.3.50 compiler, Java使用OpenJDK 6u79
image.png -
Constant pool 比较,右图Kotlin,左图Java
image.png
- Kotlin @Metadata 解析
Metadata | KotlinClassHeader | 相关描述 |
---|---|---|
k | kind | 注解标注目标类型,例如类、文件等等 |
mv | metadataVersion | 该元数据的版本 |
bv | bytecodeVersion | 字节码版本 |
d1 | data | 自定义元数据 |
d2 | strings | 自定义元数据补充字段 |
xi | extraInt | 加入的附加标记,标记类文件的来源类型 |
简要说明Kotlin Class 文件结构
Main.class
Classfile /tmp/1405968806779468952/classes/Main.class
Last modified May 28, 2020; size 566 bytes
MD5 checksum b1d7f50abdea6527c5c67d5b34b227a0
Compiled from "Main.kt"
public final class Main
SourceFile: "Main.kt"
minor version: 0 // 副版本号
major version: 50 //主版本号
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER // flags 为一种掩码的访问标志,用于表示某个类,接口
// ACC_PUBLIC: 声明为public,可以被包外访问,ACC_FINAL:声明为final,不允许有子类,[ACC_SUPER](https://blog.csdn.net/jokkkkk/article/details/86648610): 这个标志是为了纠正 invokespecial 在调用父类方法存在的版本问题。invokespecial是一个调用方法的字节码指令,用在调用构造方法、本类private方法、父类非虚方法3种场景
Constant pool:
#1 = Utf8 Main
#2 = Class #1 // Main
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 main
#6 = Utf8 ()V
#7 = Utf8 Hello
#8 = String #7 // Hello
#9 = Utf8 m
#10 = Utf8 Ljava/lang/String;
#11 = Utf8 this
#12 = Utf8 LMain;
#13 = Utf8 <init>
#14 = NameAndType #13:#6 // "<init>":()V
#15 = Methodref #4.#14 // java/lang/Object."<init>":()V
#16 = Utf8 Lkotlin/Metadata;
#17 = Utf8 mv
#18 = Integer 1
#19 = Integer 15
#20 = Utf8 bv
#21 = Integer 0
#22 = Integer 3
#23 = Utf8 k
#24 = Utf8 d1
#25 = Utf8 \u0000�\n���\n��\u0000\n��\n����\u00002�0�B�¢����J�����0�
#26 = Utf8 d2
#27 = Utf8
#28 = Utf8 Main.kt
#29 = Utf8 Code
#30 = Utf8 LineNumberTable
#31 = Utf8 LocalVariableTable
#32 = Utf8 SourceFile
#33 = Utf8 RuntimeVisibleAnnotations
{
public Main();
descriptor: ()V
// 括号是空的,代表无参数 ,V 代表无返回值。
// 如果是 descriptor: I 代表该变量的类型 是 int。
// 或者 descriptor: java.lang.string ?: 代表该变量的类型 是string。
flags: ACC_PUBLIC // flags: 代表该方法的修饰情况,ACC_PUBLIC 声明为public,可以从包外访问。
Code:
stack=1, locals=1, args_size=1
start local 0 // Main this
0: aload_0 // 从局部变量中加载索引为0的变量的值 (也指引用类型值)。 即this 的引用,压入栈 。
1: invokespecial #15 // Method java/lang/Object."<init>":()V
// invokespecial 出栈,调用java/lang/Object."<init>":()V 初始化对象,就是this指定的对象的init()方法完成初始化。
4: return
end local 0 // Main this
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public final void main();
descriptor: ()V
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=2, args_size=1
// stack = 1 表示操作数栈深度为2;locals=2, 本地变量为2个;args_size=1 表示有一个参数(默认为this)
start local 0 // Main this
0: ldc #8 // String Hello
// ldc 将String类型 “Hello” 常量值,从常量池 #8 推送至栈顶
2: astore_1
//将栈顶引用型数值存入第二个变量
start local 1 // java.lang.String m
3: return
end local 0 // Main this
end local 1 // java.lang.String m
LineNumberTable:
// LineNumberTable:指令与代码行数的偏移对应关系,每一行第一个数字对应代码行数,第二个数字对应前面code中指令
// (注意: Javap -c中显示的东西叫做 指令。)
// 也就是说Main.java文件中 第6行的代码 在执行顺序第 0 行。 Line 6: 就是第6行
line 6: 0
line 7: 3
LocalVariableTable:
// 代表它是 局部变量表,start+length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0
// 到结尾10)slot就是这个变量在局部变量表中的槽位(槽位可复用),name是变量名称,Signatur是局部变量类型描述
Start Length Slot Name Signature
3 1 1 m Ljava/lang/String;
0 4 0 this LMain;
}
RuntimeVisibleAnnotations:
0: #16(#17=[I#18,I#18,I#19],#20=[I#18,I#21,I#22],#23=I#18,#24=[s#25],#26=[s#12,s#27,s#6,s#5,s#27])
Kotlin的协程在虚拟机上实现的原理
Kotlin 协程
- 默认情况,协程运行在一个共享的线程池中。 线程仍然存在于基于协程的程序中,但是一个线程可以运行大量的协程,所以这里不需要太多线程。
- 本质上,协程是轻量级的线程。 它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动。
这里我们在 GlobalScope 中启动了一个新的协程,这意味着新协程的生命周期只受整个应用程序的生命周期限制。
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
这是因为 delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。
delay 为什么不会造成线程阻塞。
- 协程术语
- 协程: 可挂起计算的实例。
- 挂起函数 : suspend 修饰符标记的函数。
它就像一个普通的函数类型,但具有 suspend 修饰符。 - 挂起 lambda 表达式 : 必须在协程中运行的代码块。
- 挂起函数类型 : 表示挂起函数以及挂起 lambda 表达式的函数类型,
举个例子,suspend () -> Int 是一个没有参数、返回 Int 的挂起函数的函数类型。
一个声明为 suspend fun foo() : Int 的挂起函数符合上述函数类型。 - 协程构建器 : 使用一些挂起 lambda 表达式作为参数来创建一个协程的函数,并且可选地,还提供某种形式以访问协程的结果。
例如,用例中的 launch{}、future{} 以及 sequence{} 就是协程构建器。 - 挂起点 : 协程执行过程中可能被挂起的位置。
从语法上说,挂起点是对一个挂起函数的调用,但实际的挂起在挂起函数调用了标准库中的原始挂起函数时发生。 -
续体 : 是挂起的协程在挂起点时的状态.
image.png
Suspend 挂起函数原理
挂起函数不会阻塞线程,其原理是kotlin编译层面的设计
Lambda 表达式
-
Kotlin中 Lambda 表达式的约定。
image.png
线程和协程
-
线程 线程拥有独立的栈、局部变量,基于进程的共享内存,因此数据共享比较容易,但是多线程时需要加锁来进行访问控制,不加锁就容易导致数据错误,但加锁过多又容易出现死锁。
线程之间的调度由内核控制(时间片竞争机制),程序员无法介入控制。线程之间的切换需要深入到内核级别,因此线程的切换代价比较大,表现在:- 线程对象的创建和初始化
- 线程上下文切换
- 线程状态的切换由系统内核完成
- 对变量的操作需要加锁
image.png
-
协程 协程是跑在线程上的优化产物,被称为轻量级 Thread,拥有自己的栈内存和局部变量,共享成员变量。
Coroutine 可以用来直接标记方法,由程序员自己实现切换,调度,不再采用传统的时间段竞争机制。
在一个线程上可以同时跑多个协程,同一时间只有一个协程被执行,在单线程上模拟多线程并发,协程何时运行,
何时暂停,都是有程序员自己决定的,使用: yield/resume API,优势如下:- 因为在同一个线程里,协程之间的切换不涉及线程上下文的切换和线程状态的改变,不存在资源、数据并发,所以不用加锁,只需要判断状态就OK,所以执行效率比多线程高很多。
- 协程是非阻塞式的(也有阻塞API),一个协程在进入阻塞后不会阻塞当前线程,当前线程会去执行其他协程任务。
image.png
参考
在线查看字节码
注解 Metedate 参考
jvms7.pdf
协程相关参考
[协程参考] (https://github.com/Kotlin-zh/KEEP/blob/master/proposals/coroutines.md)
网友评论