阅读 96

Kotlin开发中的一些Tips(二)

接着上一篇,最近又整理了一些。

1.作用域函数选择

目前有let、run、with、apply 和 also五个作用域函数。

官方文档有张表来说明它们之间的区别: 在这里插入图片描述 总结一下有几点区别:

  • applyalso返回上下文对象。

  • letrunwith返回 lambda 结果。

  • letalso引用对象是it ,其余是this。

1.letrun是我日常使用最多的两个,它们之间很类似。

private var textView: TextView? = null textView?.let { it.text = "Kotlin" it.textSize = 14f } textView?.run { text = "Kotlin" textSize = 14f } 复制代码

相比较来说使用run显得比较简洁,但let的优势在于可以将it重命名,提高代码的可读性,也可以避免作用域函数嵌套时导致混淆上下文对象的情况。

2.对于可空对象,使用let比较方便。对于非空对象可以使用with

3.applyalso也非常相似,文档给出的建议是如果是对象配置操作使用apply,额外的处理使用also。例如:

val numberList = mutableListOf<Double>() numberList.also { println("Populating the list") }     .apply {         add(2.71)         add(3.14)         add(1.0)     }     .also { println("Sorting the list") }     .sort() 复制代码

简单说就是符合单词的含义使用,提高代码可读性。

总的来说,这几种函数有许多重叠的部分,因此可以根据开发中的具体情况来使用。以上仅做参考。

2.Sequence

我们经常会使用到kotlin的集合操作符,比如 mapfilter 等。

list.map { it * 2 }.filter { it % 3 == 0 } 复制代码

老规矩,看一下反编译后的代码: 在这里插入图片描述 就干了这么点事情,创建了两个集合,循环了两遍。效率太低,这还不如自己写个for循环,一个循环就处理完了。看一下map的源码:

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {     return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform) } public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {     for (item in this)         destination.add(transform(item))     return destination } 复制代码

内部实现确实如此,难道这些操作符不香了?

其实这时就可以使用Sequences(序列),用法很简单,只需要在集合后添加一个asSeqence() 方法。

list.asSequence().map { it * 2 }.filter { it % 3 == 0 } 复制代码

反编译:

SequencesKt.filter(SequencesKt.map(CollectionsKt.asSequence((Iterable)list), (Function1)null.INSTANCE), (Function1)null.INSTANCE); 复制代码

有两个Function1,其实就是lambda表达式,这是因为Sequence没有使用内联导致的。我们先看看SequencesKt.map源码:

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {     return TransformingSequence(this, transform) } internal class TransformingSequence<T, R> constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {     override fun iterator(): Iterator<R> = object : Iterator<R> {         val iterator = sequence.iterator()         override fun next(): R {             return transformer(iterator.next())         }         override fun hasNext(): Boolean {             return iterator.hasNext()         }     }     internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {         return FlatteningSequence<T, R, E>(sequence, transformer, iterator)     } } 复制代码

可以看到没有创建中间集合去循环,只是创建了一个Sequence对象,里面实现了迭代器。SequencesKt.filter方法也是类似。细心的话你会发现,这都只是创建Sequence对象,所以要想真正拿到处理后的集合,需要添加toList()这种末端操作。

mapfilter 这类属于中间操作,返回的是一个新Sequence,里面有数据迭代时的实际处理。而 toListfirst这类属于末端操作用来返回结果。

所以Sequence是延迟执行的,这也就是它为何不会出现我们一开始提到的问题,一次循环就处理完成了。

总结一下Sequence的使用场景:

  • 有多个集合操作符时,建议使用Sequence

  • 数据量大的时候,这样可以避免重复创建中间集合。这个数据量大,怎么也是万以上的级别了。

所以对于一般Android开发中来说,不使用Sequence其实差别不大。。。哈哈。。

3.协程

有些人会错误理解kotlin的协程,觉得它的性能更高,是一种“轻量级”的线程,类似go语言的协程。但是如果你细想一下,这是不太可能的,最终它都是要在JVM上运行,java都没有的东西,你就实现了,你这不是打java的脸嘛。

所以对于JVM平台,kotlin的协程只能是对Thread API的封装,和我们用的Executor类似。所以对于协程的性能,我个人也认为差别不大。只能说kotlin借助语言简洁的优势,让操作线程变的更加简单。

之所以上面说JVM,是因为kotlin还有js和native平台。对于它们来说,或许可以实现真正的协程。

推荐扔物线大佬关于协程的文章,帮你更好的理解kotlin的协程:到底什么是「非阻塞式」挂起?协程真的更轻量级吗?

4.Checked Exception

这对熟悉Java的同学并不陌生,Checked Exception 是处理异常的一种机制,如果你的方法中声明了它可能会抛出的异常,编译器就会强制开发者对异常进行处理,否则编译不会通过。 我们需要使用 try catch 捕获异常或者使用 throws 抛出异常处理它。

但是Kotlin中并不支持这个机制,也就是说不会强制你去处理抛出的异常。至于Checked Exception 好不好,争议也不少。这里就不讨论各自的优缺点了。

既然Kotlin中没有这个机制已经是既成事实,那么我们在使用中就需要考虑它带来的影响。比如我们开发中在调用一些方法时,要注意看一下源码中是否有指定异常抛出,然后做相应处理,避免不必要的崩溃。

例如常用的json解析:

 private fun test() {         val jsonObject = JSONObject("{...}")         jsonObject.getString("id")         ...     } 复制代码

在java中我们需要处理JSONException,kotlin中因为没有Checked Exception,如果我们像上面这样直接使用,虽然程序可以运行,可是一但解析出现异常,程序就会崩溃。

5.Intrinsics检查

如果你经常观察反编译后的java代码,会发现有许多类似Intrinsics.checkXXX这样的代码。

 fun test(str: String) {         println(str)     } 复制代码

反编译: 在这里插入图片描述 比如图中的checkParameterIsNotNull就是用了检查参数是否为空。虽然我们的参数是不可空的,但是考虑到方法会被Java调用,Kotlin会默认的增加checkParameterIsNotNull校验。如果kotlin方法是私有的,也就不会有此行检查。

checkParameterIsNotNull并不会有性能问题,相反这种提前判断参数是否正确,可以避免程序向后执行导致不必要的资源消耗。

当然如果你想去除它,可以添加下面的配置到你的gradle文件,这样就会在编译时去除它。

 kotlinOptions {         freeCompilerArgs = [                 '-Xno-param-assertions',                 '-Xno-call-assertions',                 '-Xno-receiver-assertions'         ]     }


作者:唯鹿
链接:https://juejin.cn/post/7022429745874731045


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐