美文网首页
Kotlin Vocabulary

Kotlin Vocabulary

作者: Drew_MyINTYRE | 来源:发表于2020-12-31 10:27 被阅读0次

    Collection 和 Sequence

    Kotlin 提供了基于不同执行方式的两种集合类型: 立即执行 (eagerly) 的 Collection 类型,延迟执行 (lazily) 的 Sequence 类型。
    立即执行和延迟执行的区别在于每次对集合进行转换时,这个操作会在何时真正执行。

    Collection 会立即执行对数据的操作,而 Sequence 则是延迟执行。根据要处理的数据量大小,选择最合适的一个: 数据量小,则使用 Collection,数据量大,则使用 Sequence,另外,需注意操作顺序带来的影响。

    //Collections
    public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
      return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
    }
    
    public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R>{      
       return TransformingSequence(this, transform)
    }
    

    不同之处:

    Collection 的操作使用了内联函数,所以处理所用到的字节码以及传递给它的 lambda 字节码都会进行内联操作。而 Sequence 不使用内联函数,因此,它会为每个操作创建新的 Function 对象。

    640.gif

    看图得知:当列表数据很大时,中间集合的创建会很消耗资源,这种情况下就应该使用 Sequence。

    密封类 sealed class

    sealed class (本文下称 "密封类" ) 则同时包含了前面两者的优势 —— 抽象类表示的灵活性和枚举里集合的受限性。

    sealed class Result<out T : Any> {
      data class Success<out T : Any>(val data: T) : Result<T>()
      sealed class Error(val exception: Exception) : Result<Nothing>() {
         class RecoverableError(exception: Exception) : Error(exception)
         class NonRecoverableError(exception: Exception) : Error(exception)
      }
      object InProgress : Result<Nothing>()
    }
    

    工作原理:

    //反编译 一下:
    sealed class Result
    data class Success(val data: Any) : Result()
    data class Error(val exception: Exception) : Result()
     
    @Metadata(
       ...
       d2 = {"Lio/testapp/Result;", "T", "", "()V", "Error", "Success", "Lio/testapp/Result$Success;", "Lio/testapp/Result$Error;" ...}
    )
     
    public abstract class Result {
       private Result() {
       }
     
       // $FF: synthetic method
      //一个合成构造方法,只有 Kotlin 编译器可以使用
      //这意味着其他的类无法直接调用密封类的构造方法。
       public Result(DefaultConstructorMarker $constructor_marker) {
          this();
       }
    }
    
    public final class Success extends Result {
       @NotNull
       private final Object data
     
       public Success(@NotNull Object data) {
          Intrinsics.checkParameterIsNotNull(data, "data");
          //可以看到它调用了 Result 的合成构造方法
          super((DefaultConstructorMarker)null);
          this.data = data;
       }
    

    类型别名 typealias

    使用类型别名时,需要思考是否有必要这么做: 在这里使用类型别名真的会让您的代码意义更明确、可读性更好吗?
    类型别名需要在类的外部声明,所以使用时您需要考虑约束它们的可见性。

    //使用的某个类名称很长,您可以使用类型别名来缩短它:
    typealias AVD = AnimatedVectorDrawable
    
    //在此示例中,使用导入别名 (import alias) 会更加合适:
    import android.graphics.drawable.AnimatedVectorDrawable as AVD
    

    枚举和 R8 编译器

    Kotlin 最终会编译为 Java 字节码,但是它却提供了 Java 所没有的功能。那么 Kotlin 是怎么做到的呢?这些功能有没有额外开销?如果有,我们能做些什么来优化它吗?

    事实上,有三个编译器参与了 Android 应用中 Kotlin 代码的编译。

    • Kotlin 编译器

    Kotlin 编译器将会首先运行,它会把您写的代码转换为 Java 字节码。虽然听起来很棒,但可惜的是 Android 设备上并不运行 Java 字节码,而是被称为 DEX 的 Dalvik 可执行文件。Dalvik 是 Android 最初所使用的运行时。而 Android 现在的运行时,则是从 Android 5.0 Lollipop 开始使用的 ART (Android Runtime),不过 ART 依然在运行 DEX 代码 (如果替换后的运行时无法运行原有的可执行文件的话,就毫无兼容性可言了)。

    • D8

    D8 是整个链条中的第二个编译器,它把 Java 字节码转换为 DEX 代码。到了这一步,您已经有了能够运行在 Android 中的代码。不过,您也可以选择继续使用第三个编译器 —— R8。

    • R8 (可选,但推荐使用)

    R8 以前是用来优化和缩减应用体积的,它基本上就是 ProGuard 的一个替代方案。R8 不是默认开启的,如果您希望使用它 (例如您想要这里讨论到的那些优化时),就需要启用它。在模块的 build.gradle 里添加 minifyEnabled = true ,就可以强制打开 R8 。它将在所有其他编译工作后执行,来保证您获得的是一个缩减和优化过的应用。

    内联类 inline class

    interface Id
    inline class DoggoId(val id: Long) : Id {
      val stringId
      get() = id.toString()
    
      fun isValid()= id > 0L
    }
    

    内联函数的原理与应用

    每个高阶函数都会造成函数对象的创建和内存的分配,从而带来额外的运行时开销。

    Reified: 类型擦除后再生计划

    泛型提供了类型安全,并帮助开发者在编程时不需要进行显示的类型转换。但是使用泛型也会有一些限制,比如当您在泛型函数中想要获取泛型所表示类型的具体信息时,编译器就会报错,提示说相关的信息不存在。为了达到这一目标,Kotlin 提供了一个特别的关键字 reified,使用它就可以在泛型函数中获取所需的类型信息。

    没利用reified关键字,解决这一问题的一个方法,是将泛型实际代表的类型信息作为一个参数传递给函数。

    fun <T> printType(classType: Class<T>) {
        print(classType::class.java)
    }
    

    历史背景:

    在 Java 5.0 版本之前并未支持泛型,那时 Java 中的 collection 是没有类型信息的。也就是说一个 ArrayList 并不会声明它内部所包含的数据类型到底是 String、Integer 还是别的类型。
    在没有泛型支持时,任何时候想访问 collection 中的对象,都要做一次显式的类型转换。另外也没有相应的错误保障机制来防止出现非法的类型转换。

    List list = new ArrayList();
    list.add("First String");
    // 正常处理,没有错误
    list.add(6); 
    
    String str = (String)list.get(1); 
    // 需要显示地进行转换和抛出异常
    

    类型擦除

    泛型是通过一种叫类型擦除 (type erasure) 的技巧实现的。由于 Java 5 之前没有关联类型信息,编译器会先将所有类型替换为基本的 Object 类型,然后再进行必要的类型转换。通过将类型信息提供给编译器,类型擦除可以做到既保证编译时类型安全,又可以通过保持字节码同之前的 Java 版本相同来实现向后兼容。

    Reified 关键字必须结合内联函数一起使用,它能让本该在编译阶段就被擦除的类型信息,能够在运行时被获取到。

    如果一个函数被标记为 inline,那么 Kotlin 编译器会在所有使用该函数的地方将函数调用替换为函数体。这样做的好处是,编译器可以随意地在调用处对函数体进行修改,因为修改的函数体是被复制的,所以修改后不会影响到其余调用同样函数的地方。若是要在参数中使用 reified,那首先需要将函数标记为 inline,然后在泛型参数之前添加 reified 关键字即可。

    inline fun <reified T> printType() {
      print(T::class.java)
    }
    
    fun printStringType(){
     // 用 String 类型调用被 reified 修饰的泛型函数
      printType<String>()  
    }
    

    另外请牢记,Java 代码中不能访问被 reified 修饰的函数。Java 不支持内联,也就意味着在 Java 中的泛型参数不能逃脱被编译器擦除类型的命运。

    Reified 允许您在使用泛型来进行编程的同时,还能够在运行时获取到泛型所代表的类型信息,这在之前是无法做到的。当您需要在内联函数中使用到类型信息,或者需要重载泛型返回值时,您可以使用 reified。使用 reified 不会带来任何性能上的损失,但是如果被内联的函数过于复杂则,还是可能会导致性能问题。因为 reified 必须使用内联函数,所以要保证内联函数的简短,并且遵循使用内联函数的最佳实践,以免让性能受到损失。

    相关文章

      网友评论

          本文标题:Kotlin Vocabulary

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