Kotlin Internals

作者: Kenber | 来源:发表于2019-06-20 19:23 被阅读144次

    Understanding what's under the hood of Kotlin

    😃 优点

    • 简约,更少的模板代码

    • 强大的扩展库(Anko等)

    • 与Java和Android API完全兼容

    • 现代编程语言特性

    🎯 目标

    1. Kotlin优秀特性的内部实现原理、与Java差异、性能开销等

    2. 如何使用工具探明目标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实现步骤总结:
    1. 生成一个该属性的附加代理:xxx$delegate;

    2. 在构造器中,将使用lazy(()->T)创建的Lazy实例对象赋值给xxx$delegate;

    3. 当该属性被调用,即其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

    Coroutines on Android (part II): Getting started

    Coroutines On Android (part III): Real work

    相关文章

      网友评论

        本文标题:Kotlin Internals

        本文链接:https://www.haomeiwen.com/subject/ebxsqctx.html