kotlin 中的使用小技巧總結(jié)
作者:小羊子說
來源:SegmentFault 思否
1.kotlin中l(wèi)ateinit和by lazy的區(qū)別
lazy { ... }只能被用在被val修飾的變量上,而lateinit只能被用var修飾的變量上,因為被lateinit修飾的字段無法被編譯為一個final字段、因此無法保證它的不可變性。
被lateinit修飾的變量有一個幕后字段用來存儲它的值,而by lazy { ... }創(chuàng)建了一個包含by lazy { ... }中代碼返回值的實例對象,實例對象持有這個值并生成一個可以在實例對象中調(diào)用的這個值的getter。所以如果你需要在代碼中使用幕后字段的話,使用lateinit
被lateinit修飾的變量可以在對象(代碼)的任何地方進行初始化,而且同一個類的不同對象可以對這個變量進行多次的初始化(賦值)。但是,對于by lazy { ... }修飾的變量,只擁有唯一一個聲明在{}中的初始化構(gòu)造器,如果你想要修改它,你只能通過在子類中覆寫的方式來修改它的值。 所以,如果你想要你的屬性在其他地方以不是你事先定義好的值初始化的話,使用lateinit by lazy { ... }的初始化默認是線程安全的,并且能保證by lazy { ... }代碼塊中的代碼最多被調(diào)用一次。而lateinit var默認是不保證線程安全的,它的情況完全取決于使用者的代碼。 Lazy實例是有值的,這個值可以被存儲、傳遞和使用。但是,被lateinit var修飾的變量不存儲任何多余的運行時狀態(tài),只有值還未被初始化的null值。 如果你持有一個Lazy實例的引用,你可以使用它的isInitialized()方法來判斷它是否已經(jīng)被初始化。從Kotlin1.2開始,你也可以使用方法引用的方式來獲取這個值。 by lazy { ... }中傳遞的lambda表達式可能會捕獲它的閉包中使用的上下文的引用,引用會一直被持有直到變量被初始化。因此這樣可能會導致內(nèi)存泄漏,所以仔細考慮你在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
這個關(guān)鍵字其實使用的很多,在定義全局變量為空的時候并不是非得用問號設(shè)置為可空的,如果你可以確定一定不為空可以使用 lateinit 這個關(guān)鍵字來定義全局變量,舉個栗子:
? ? ? ? ? ? ? ? ??
lateinit var zhuJ: ZhuJ當這樣定義全局變量的時候就無需設(shè)置為可空了,比如安卓項目中的 adapter ,咱們肯定能確認會賦值,不會為空,那么就可以使用 lateinit 了。
這塊需要注意的是,即使咱們覺得不會為空,但肯定會有特殊情況需要進行判斷,需要進行判斷的話要使用 isInitialized ,使用方法如下:
? ? ? ? ? ? ? ?
if?(::zhuJ.isInitialized){// 判斷是否已經(jīng)進行賦值}
2.!! 和?.的區(qū)別
**"?"加在變量名后,系統(tǒng)在任何情況不會報它的空指針異常。"!!"加在變量名后,如果對象為null,那么系統(tǒng)一定會報異常!**
?: 對象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的區(qū)別
Kotlin之let,apply,run,with等函數(shù)區(qū)別2
使用實例1:
? ? ? ? ? ? ? ?
//普通使用var user = User()user.id = 1user.name = "test1"user.hobbies = listOf("aa", "bb", "cc")println("user = $user")user.let {it.id = 2it.name = "test2"it.hobbies = listOf("aa", "bb", "cc")}println("user = $user")user.also {it.id = 3it.name = "test3"it.hobbies = listOf("aa", "bb", "cc")}println("user = $user")user.apply {id = 2name = "test2"hobbies = listOf("aa", "bb", "cc")Date()}println("user = $user")user.run {id = 3name = "test3"hobbies = listOf("aa", "bb", "cc")Date()}println("user = $user")with(user) {id = 4name = "test4"hobbies = listOf("aa", "bb", "cc")Date()}println("user = $user")
使用實例2:
一個http的response結(jié)構(gòu)體。
? ? ? ? ? ? ? ?
class Resp{ var code: Int = 0var body: T? = nullvar errorMessage: String? = nullfun isSuccess(): Boolean = code == 200override fun toString(): String {return "Resp(code=$code, body=$body, errorMessage=$errorMessage)"}}
在處理網(wǎng)絡(luò)數(shù)據(jù)的時候,需要各種判斷,比如。
? ? ? ? ? ? ? ?
fun main(args: Array) { var resp: Resp? = Resp() if (resp != null) {if (resp.isSuccess()) {// do successprintln(resp.body)} else {// do failprintln(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 successprintln(resp.body)} else {println(resp.errorMessage)}}resp?.apply {if (isSuccess()) {// do successprintln(resp.body)} else {println(resp.errorMessage)}}resp?.let {if (it.isSuccess()) {// do successprintln(it.body)} else {println(it.errorMessage)}}resp?.also {if (it.isSuccess()) {// do successprintln(it.body)} else {println(it.errorMessage)}}}
4.as運算符和as?運算符
as運算符用于執(zhí)行引用類型的顯式類型轉(zhuǎn)換。如果要轉(zhuǎn)換的類型與指定的類型兼容,轉(zhuǎn)換就會成功進行;
如果類型不兼容,使用as?運算符就會返回值null。在Kotlin中,父類是禁止轉(zhuǎn)換為子類型的。
項目中運用
? ? ? ? ? ? ? ? ??
private fun initBuyDialog(): BuyDialog {//as?如果不兼容 會返回為null ?:為空時會初始化return supportFragmentManager.findFragmentByTag(BuyDialog.TAG) as? BuyDialog ?: BuyDialog()}
5. kotlin 中關(guān)于null的處理
? ? ? ? ? ? ? ? ??
Kotlin異常:method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull場景:java代碼調(diào)用kotlin方法,kotlin方法參數(shù)后邊不加?,且實參為null
fun kotlinFun(arg1:String,...)java代碼中調(diào)用kotlin方法kotlinFun,如果參數(shù)傳null,就會直接拋如題異常
原因:kotlin的空安全機制,如果參數(shù)后邊不加?,則該參數(shù)為非空參數(shù),實參為null就會拋如題異常.
解決辦法:kotlin方法參數(shù)加?,接受null空參數(shù)
? ? ? ? ? ? ? ? ??
fun kotlinFun(arg1:String?,...)關(guān)于服務器返回的null值的優(yōu)雅處理:
? ? ? ? ? ? ? ?
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,而是拋出一個空指針異常,你就可以使用它。
空引用的調(diào)用,下面還有第三種方面來調(diào)用它的成員函數(shù)和變量。
6.優(yōu)雅的處理空字符串
重點:ifEmpty{}
當字符串為空字符串的時候,返回一個默認值,常見的寫法如下所示:
? ? ? ? ? ? ? ?
val target = ""val name = if (target.isEmpty()) "dhl" else target
其實有一個更簡潔的方法,可讀性更強,使用 ifEmpty 方法,當字符串為空字符串時,返回一個默認值,如下所示。
val?name?=?target.ifEmpty?{?"dhl"?}其原理跟我們使用 if 表達式是一樣的,來分析一下源碼。
? ? ? ? ? ? ? ?
public inline funC.ifEmpty(defaultValue: () -> R): R where C : CharSequence, C : R = if (isEmpty()) defaultValue() else this
ifEmpty 方法是一個擴展方法,接受一個 lambda 表達式 defaultValue ,如果是空字符串,返回 defaultValue,否則不為空,返回調(diào)用者本身。
除了 ifEmpty 方法,Kotlin 庫中還封裝很多其他非常有用的字符串,例如:將字符串轉(zhuǎn)為數(shù)字。常見的寫法如下所示:
? ? ? ? ? ? ? ?
val input = "123"val number = input.toInt()
其實這種寫法存在一定問題,假設(shè)輸入字符串并不是純數(shù)字,例如 123ddd 等等,調(diào)用 input.toInt() 就會報錯,那么有沒有更好的寫法呢?如下所示。
? ? ? ? ? ? ? ?
val input = "123"// val input = "123ddd"// val input = ""val number = input.toIntOrNull() ?: 0
7.sealed
這個關(guān)鍵字之前一直沒有進行使用,它用來修飾類,含義為密封類,之前一直沒搞懂這個密封類有啥說啥用,這兩天好好看了下,我理解的作用就是:可以使代碼更加嚴密。
這樣說感覺有點抽象,再舉個栗子吧,平時咱們在封裝一些工具的時候一般只會有成功和失敗,咱們的做法一般是定義一個接口,然后再定義一個成功類和失敗類來實現(xiàn)這個接口,最后再進行判斷:
? ? ? ? ? ? ? ?
class Success(val msg: String) : Resultclass Fail(val error: Throwable) : Resultfun getResult(result: Result) = when (result) {is Success -> result.msgis Fail -> result.error.messageelse -> throw IllegalArgumentException()}
上面代碼都是咱們一般寫的,雖然只有兩種情況,但是必須再寫 else 來進行判斷,如果不寫的話編譯就過不了。但如果使用密封類的話就不會有這種情況出現(xiàn):
? ? ? ? ? ? ? ?
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 會檢查條件是否包含了所有的子類,如果沒有會提示你加上,這樣就大大提高的代碼的魯棒性,也不會出現(xiàn)沒有判斷到的問題。


