Understanding what's under the hood of Kotlin
😃 优点
-
简约,更少的模板代码
-
强大的扩展库(Anko等)
-
与Java和Android API完全兼容
-
现代编程语言特性
🎯 目标
-
Kotlin优秀特性的内部实现原理、与Java差异、性能开销等
-
如何使用工具探明目标1
📝 工具
-
Kotlin Bytecode
-
Java Decompilation
-
举个栗子🌰
可空类型Int?自动装箱:
反编译字节码验证:
💪 Kotlin特性及实现原理
-
Lazy Init
-
Java普通实现方式
private Integer lazyInt;
public final int getLazyInt() {
if (this.lazyInt == null) {
this.lazyInt = 666;
}
return lazyInt;
}
-
Kotlin实现
private lateinit var lazyInt: Int
声明var为lateinit,让编译器在编译检查时不会因为属性变量未被初始化而报错,后续手动赋值初始化
val lazyInt by lazy { 666 }
声明val(一旦被(延迟)初始化不允许再被修改)为by lazy,后续由编译器自动实现延迟初始化
-
by lazy的内部实现
//KProperty[]属性数组,Kotlin独有,用于获取Kotlin反射得到的属性,定义反射所需各种参数
static final KProperty[] $$delegatedProperties = new KProperty[]{
(KProperty)Reflection.property0(new PropertyReference0Impl(
Reflection.getOrCreateKotlinPackage(LazyInitKt.class, "app_debug"), "lazyInt", "getLazyInt()I"))};
//编译生成属性委托
@NotNull
private static final Lazy lazyInt$delegate;
static {
//lazyInt$2实现了Function0接口 ,name$2.INSTANCE是该实现类的实例对象
lazyInt$delegate = LazyKt.lazy((Function0)lazyInt$2.INSTANCE);
}
public static final int getLazyInt() {
Lazy var0 = lazyInt$delegate;
return ((Number)var0.getValue()).intValue();
}
-
getValue()实现:真正的延迟初始化工作
private class SynchronizedLazyImpl<out T> {
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
-
by lazy实现步骤总结:
-
生成一个该属性的附加代理:xxx$delegate;
-
在构造器中,将使用lazy(()->T)创建的Lazy实例对象赋值给xxx$delegate;
-
当该属性被调用,即其getter方法被调用时返回属性值:
delegate.getVaule()方法的返回结果是对象xxx$delegate内部的_value属性值,在getVaule()第一次被调用时会将_value进行初始化,往后都是直接将_value的值返回,从而实现属性值的唯一一次初始化。
-
lateinit VS by lazy:
lateinit无法像by lazy那样自动延迟初始化,且只能用来修饰类属性和对象,不能用于修饰局部量和基本类型(类加载后准备阶段被初始化为默认值),但by lazy从实现来看有一点点代价
-
Inline Class and Unsigned Numbers
-
Inline Class
有时候,业务逻辑需要围绕某种类型创建包装器。然而,由于额外的堆内存分配问题,它会引入运行时的性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就进行了大量优化,然而他们的包装器却没有得到任何特殊的处理,为此Kotlin在v1.3引入Inline Class——内联类
内联类要求有且仅有一个构造函数的参数,内联类编译期消失,唯一的构造器参数作为内联类实例化时的返回值,内连类的新增方法会变为静态函数,内联类属性的get方法也会变为静态方法被调用,相当于去除包装,直接嵌入实际值或静态调用实现,解决了包装器类在运行时的性能损耗
-
Unsigned Numbers
Kotlin v1.3引的入实验特性(More Details),相对于Java的Signed Numbers在图形相关的编码上更有用
val width = 10u
val height = 20u
val area = width * height
自带类型检查,不会得到无用的负值
-
实现原理
-
基本算数运算完全沿用Java的Signed Numbers中的:IADD(+)、ISUB(-)、IMUL(*),仅/的实现为Kotlin.UnsignedKt.uintDivide
-
使用inline class实现,赋值时转为UInt返回,因相应class没有toString实现,故需要做装箱转化,实现如下
-
/****Kotlin****/
val b = 6u
println(b)
/****Decompile Java****/
//做装箱包装成Java中的UInt,UInt重写了toString方法才能打印
UInt var = UInt.box-impl(b)
System.out.println(var)
-
Inline Function
-
内联方法的函数体被直接嵌入调用点,字节码中不会产生针对被调用方法的invoke
-
另外inline与含有函数类型参数的方法一起使用,能够避免匿名内部类对象的创建
用高阶函数实现过滤列表中奇数:
val list = listOf(1, 2, 3, 4, 5)
fun main() {
filterList {
it % 2 == 1
}
}
//非内联高阶函数,参数用Lambda表达式声明一个函数
fun filterList(predicate: (Int) -> Boolean) {
list.filter(predicate)
}
将相应字节码反编译后的Java实现:
@NotNull
private static final List list = CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5});
@NotNull
public static final List getList() {
return list;
}
public static final void main() {
filterList((Function1)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
//Function1类型的predicate被实例化
public static final void filterList(@NotNull Function1 predicate) {
Intrinsics.checkParameterIsNotNull(predicate, "predicate");
Iterable $this$filter$iv = (Iterable)list;
Collection destination$iv$iv = (Collection)(new ArrayList());
Iterator var6 = $this$filter$iv.iterator();
while(var6.hasNext()) {
Object element$iv$iv = var6.next();
if ((Boolean)predicate.invoke(element$iv$iv)) {
destination$iv$iv.add(element$iv$iv);
}
}
}
改为使用内联函数实现过滤列表中奇数:
val list = listOf(1, 2, 3, 4, 5)
fun main() {
filterList {
it % 2 == 1
}
}
//inline声明filterList为内联函数
inline fun filterList(predicate: (Int) -> Boolean) {
list.filter(predicate)
}
再次查看相应反编译结果:
@NotNull
private static final List list = CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5});
@NotNull
public static final List getList() {
return list;
}
public static final void main() {
Iterable $this$filter$iv$iv = (Iterable)getList();
Collection destination$iv$iv$iv = (Collection)(new ArrayList());
Iterator var6 = $this$filter$iv$iv.iterator();
while(var6.hasNext()) {
Object element$iv$iv$iv = var6.next();
int it = ((Number)element$iv$iv$iv).intValue();
//直接将过滤方法和条件嵌入main函数,不会产生Function1类型的predicate对象
if (it % 2 == 1) {
destination$iv$iv$iv.add(element$iv$iv$iv);
}
}
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
-
Ranges and Loop
-
几种Kotlin在ranges中做loop对应的Java实现
最后的例子使用Iterable的实现对于仅仅是某个范围的遍历显得比较繁重,但还有更糟糕的情形
step 2相当于index+=2,但编译后的实际Java实现并非如此,采取了非常没必要的实现方式;即便是step 1,对应的Java实现依旧如此,说明编译器对此没有任何优化措施,存在性能上的浪费
-
Extension Functions and Subclasses
一个看起来不符合认知(基于Java的认知)的case:
根据反编译字节码的结果来解读(毒):
看似成员方法,实则根据传入的声明类型来定返回值的静态方法,注意与Java的区别!
-
Extensional Functions
实现为现有类自由添加自定义函数
//给已有的String类扩展firstChar()函数
fun String.firstChar(): String {
if (this.isEmpty()) {
return ""
}
return this[0].toString()
}
fun main() {
val str = "123"
println(str.firstChar())
}
以静态导入的方式实现,并非真正的增加了成员函数
//receiver即上部代码中的this,在扩展函数中this代表调用函数时点号左侧传递的接收者参数
public static final String firstChar(@NotNull String $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
CharSequence var1 = (CharSequence)$receiver;
return var1.length() == 0 ? "" : String.valueOf($receiver.charAt(0));
}
作为替代xxxUtils.java的更优雅的实现
📚 总结
-
Kotlin拥有诸多现代语言的先进特性,能很大程度减少模板代码、常见的工具类、约定俗成的实现等基础性代码,逻辑代码表达也更简洁且贴近自然语言
-
Kotlin有很多语法糖,可能会带来编译变慢、运行时低效等性能开销,但也有很多特性旨在提高性能,辩证地使用Kotlin,尽量避免性能损耗,收益是远大于损失的
-
Kotlin first, not Kotlin must
🔨 扩展
Coroutines on Android:在Android开发中使用协程
Coroutines on Android (part I): Getting the background
网友评论