kotlin 中的使用小技巧总结

SegmentFault

共 9454字,需浏览 19分钟

 ·

2020-07-28 18:32


作者:小羊子说

来源:SegmentFault 思否




1.kotlin中lateinit和by lazy的区别


  • lazy { ... }只能被用在被val修饰的变量上,而lateinit只能被用var修饰的变量上,因为被lateinit修饰的字段无法被编译为一个final字段、因此无法保证它的不可变性。
  • 被lateinit修饰的变量有一个幕后字段用来存储它的值,而by lazy { ... }创建了一个包含by lazy { ... }中代码返回值的实例对象,实例对象持有这个值并生成一个可以在实例对象中调用的这个值的getter。所以如果你需要在代码中使用幕后字段的话,使用lateinit
  • 被lateinit修饰的变量可以在对象(代码)的任何地方进行初始化,而且同一个类的不同对象可以对这个变量进行多次的初始化(赋值)。但是,对于by lazy { ... }修饰的变量,只拥有唯一一个声明在{}中的初始化构造器,如果你想要修改它,你只能通过在子类中覆写的方式来修改它的值。
    所以,如果你想要你的属性在其他地方以不是你事先定义好的值初始化的话,使用lateinit
  • by lazy { ... }的初始化默认是线程安全的,并且能保证by lazy { ... }代码块中的代码最多被调用一次。而lateinit var默认是不保证线程安全的,它的情况完全取决于使用者的代码。
  • Lazy实例是有值的,这个值可以被存储、传递和使用。但是,被lateinit var修饰的变量不存储任何多余的运行时状态,只有值还未被初始化的null值。
  • 如果你持有一个Lazy实例的引用,你可以使用它的isInitialized()方法来判断它是否已经被初始化。从Kotlin1.2开始,你也可以使用方法引用的方式来获取这个值。
  • by lazy { ... }中传递的lambda表达式可能会捕获它的闭包中使用的上下文的引用,引用会一直被持有直到变量被初始化。因此这样可能会导致内存泄漏,所以仔细考虑你在lambda表达式中使用的值是否合理。


原文链接:https://stackoverflow.com/questions/36623177/kotlin-property-initialization-using-by-lazy-vs-lateinit

                  

Here are the significant differences between lateinit var and by lazy { ... } delegated property:
lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to vars, because it can't be compiled to a final field, thus no immutability can be guaranteed;
lateinit var has a backing field which stores the value, and by lazy { ... } creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. So if you need the backing field present in the class, use lateinit;
In addition to vals, lateinit cannot be used for non-nullable properties and Java primitive types (this is because of null used for uninitialized value);
lateinit var can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit.
Initialization by lazy { ... } is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy overload). In the case of lateinit var, it's up to the user's code to initialize the property correctly in multi-threaded environments.
A Lazy instance can be saved, passed around and even used for multiple properties. On contrary, lateinit vars do not store any additional runtime state (only null in the field for uninitialized value).
If you hold a reference to an instance of Lazy, isInitialized() allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property). To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2.
A lambda passed to by lazy { ... } may capture references from the context where it is used into its closure.. It will then store the references and release them only once the property has been initialized. This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.
Also, there's another way not mentioned in the question: Delegates.notNull(), which is suitable for deferred initialization of non-null properties, including those of Java primitive types.

在项目中的实际运用示例:


1.实例化对话框

注意事项

1.lateinit中的未被初始化的值为null,注意使用前检查。


lateinit


这个关键字其实使用的很多,在定义全局变量为空的时候并不是非得用问号设置为可空的,如果你可以确定一定不为空可以使用 lateinit 这个关键字来定义全局变量,举个栗子:

                  

lateinit var zhuJ: ZhuJ


当这样定义全局变量的时候就无需设置为可空了,比如安卓项目中的 adapter ,咱们肯定能确认会赋值,不会为空,那么就可以使用 lateinit 了。


这块需要注意的是,即使咱们觉得不会为空,但肯定会有特殊情况需要进行判断,需要进行判断的话要使用 isInitialized ,使用方法如下:

               

if (::zhuJ.isInitialized){    // 判断是否已经进行赋值}





2.!! 和?.的区别


**"?"加在变量名后,系统在任何情况不会报它的空指针异常。"!!"加在变量名后,如果对象为null,那么系统一定会报异常!**


?: 对象A ?: 对象B 表达式:


意思为,当对象 A值为 null 时,那么它就会返回后面的对象 B。


foo?:bar  ==>


if(foo!=bar){foo}else{bar}


foo?.bar ==>

               

if(foo!=null){foo.bar}else if(foo==null){null}





3. with、let、apply、run的区别


Kotlin之let,apply,run,with等函数区别2


使用实例1:

               

//普通使用var user = User()user.id = 1user.name = "test1"user.hobbies = listOf("aa", "bb", "cc")println("user = $user")

user.let { it.id = 2 it.name = "test2" it.hobbies = listOf("aa", "bb", "cc")}println("user = $user")
user.also { it.id = 3 it.name = "test3" it.hobbies = listOf("aa", "bb", "cc")}println("user = $user")
user.apply { id = 2 name = "test2" hobbies = listOf("aa", "bb", "cc") Date()}println("user = $user")
user.run { id = 3 name = "test3" hobbies = listOf("aa", "bb", "cc") Date()}println("user = $user")
with(user) { id = 4 name = "test4" hobbies = listOf("aa", "bb", "cc") Date()}println("user = $user")


使用实例2:


一个http的response结构体。

               

class Resp {    var code: Int = 0    var body: T? = null    var errorMessage: String? = null
fun isSuccess(): Boolean = code == 200
override fun toString(): String { return "Resp(code=$code, body=$body, errorMessage=$errorMessage)" }}


在处理网络数据的时候,需要各种判断,比如。

               

fun main(args: Array) {    var resp: Resp? = Resp()
if (resp != null) { if (resp.isSuccess()) { // do success println(resp.body) } else { // do fail println(resp.errorMessage) } }}


//用了操作符号后

               

fun main(args: Array) {    var resp: Resp? = Resp()
// if (resp != null) {// if (resp.isSuccess()) {// // do success// println(resp.body)// } else {// println(resp.errorMessage)// }// }
resp?.run { if (isSuccess()) { // do success println(resp.body) } else { println(resp.errorMessage) } }
resp?.apply { if (isSuccess()) { // do success println(resp.body) } else { println(resp.errorMessage) } }
resp?.let { if (it.isSuccess()) { // do success println(it.body) } else { println(it.errorMessage) } }
resp?.also { if (it.isSuccess()) { // do success println(it.body) } else { println(it.errorMessage) } }}





4.as运算符和as?运算符


as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;


如果类型不兼容,使用as?运算符就会返回值null。在Kotlin中,父类是禁止转换为子类型的。


项目中运用

                  

private fun initBuyDialog(): BuyDialog {        //as?如果不兼容 会返回为null  ?:为空时会初始化        return supportFragmentManager.findFragmentByTag(BuyDialog.TAG) as? BuyDialog ?: BuyDialog()    }





5. kotlin 中关于null的处理

                  

Kotlin异常:method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull


场景:java代码调用kotlin方法,kotlin方法参数后边不加?,且实参为null



fun kotlinFun(arg1:String,...)

java代码中调用kotlin方法kotlinFun,如果参数传null,就会直接抛如题异常


原因:kotlin的空安全机制,如果参数后边不加?,则该参数为非空参数,实参为null就会抛如题异常.


解决办法:kotlin方法参数加?,接受null空参数

                  

fun kotlinFun(arg1:String?,...)


关于服务器返回的null值的优雅处理:

               

val l:Int = if(b!=null){    b.length}else{    -1}


//等价于


val l = b?.length?:-1


如果b为null返回-1,否则返回b.length。

               

var b: String? = "abc"val l = b!!.length()


它的返回值有两种可能,如果b不为null,返回b.length(),否则,抛出一个空指针异常,如果b为null,你不想返回null,而是抛出一个空指针异常,你就可以使用它。


空引用的调用,下面还有第三种方面来调用它的成员函数和变量。





6.优雅的处理空字符串


重点:ifEmpty{}


当字符串为空字符串的时候,返回一个默认值,常见的写法如下所示:

               

val target = ""val name = if (target.isEmpty()) "dhl" else target


其实有一个更简洁的方法,可读性更强,使用 ifEmpty 方法,当字符串为空字符串时,返回一个默认值,如下所示。


val name = target.ifEmpty { "dhl" }


其原理跟我们使用 if 表达式是一样的,来分析一下源码。

               

public inline fun  C.ifEmpty(defaultValue: () -> R): R where C : CharSequence, C : R =    if (isEmpty()) defaultValue() else this


ifEmpty 方法是一个扩展方法,接受一个 lambda 表达式 defaultValue ,如果是空字符串,返回 defaultValue,否则不为空,返回调用者本身。


除了 ifEmpty 方法,Kotlin 库中还封装很多其他非常有用的字符串,例如:将字符串转为数字。常见的写法如下所示:

               

val input = "123"val number = input.toInt()


其实这种写法存在一定问题,假设输入字符串并不是纯数字,例如 123ddd 等等,调用 input.toInt() 就会报错,那么有没有更好的写法呢?如下所示。

               

val input = "123"//    val input = "123ddd"//    val input = ""val number = input.toIntOrNull() ?: 0





7.sealed


这个关键字之前一直没有进行使用,它用来修饰类,含义为密封类,之前一直没搞懂这个密封类有啥说啥用,这两天好好看了下,我理解的作用就是:可以使代码更加严密。


这样说感觉有点抽象,再举个栗子吧,平时咱们在封装一些工具的时候一般只会有成功和失败,咱们的做法一般是定义一个接口,然后再定义一个成功类和失败类来实现这个接口,最后再进行判断:

               

class Success(val msg: String) : Resultclass Fail(val error: Throwable) : Result
fun getResult(result: Result) = when (result) { is Success -> result.msg is Fail -> result.error.message else -> throw IllegalArgumentException()}


上面代码都是咱们一般写的,虽然只有两种情况,但是必须再写 else 来进行判断,如果不写的话编译就过不了。但如果使用密封类的话就不会有这种情况出现:

               

sealed class Resultsclass Success(val mag: String) : Results()class Failure(val error: Exception) : Results()
fun getMessage(result: Results) { when (result) { is Success -> { println(result.mag) } is Failure -> { println(result.error.toString()) } }}


不仅不用再写else,而且在进行 when 判断时,kotlin 会检查条件是否包含了所有的子类,如果没有会提示你加上,这样就大大提高的代码的鲁棒性,也不会出现没有判断到的问题。




- END -

点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流。

浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报