<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kotlin修煉指南(四)

          共 6009字,需瀏覽 13分鐘

           ·

          2020-09-23 14:25

          Kotlin這門語言極其靈活,這是一把雙刃劍,相比Java,大家寫的都是白話文,不論水平高低,大家基本都是能非常流暢的閱讀彼此的代碼的,但是在使用Kotlin之后,由于大家的Kotlin表達(dá)水平和思維習(xí)慣的不同,就好造成這樣一種情形,「這tm還能這樣寫?」、「這寫的是個(gè)啥?」、「臥槽、牛B」。

          所以下面總結(jié)了一些平時(shí)寫Kotlin時(shí),那些跟Java白話文寫的不太一樣的地方,拓展拓展大家的思維,讓開發(fā)者在寫Kotlin代碼的時(shí)候,能夠更加的有Kotlin味兒。

          Sealed Class

          Sealed Class,聽上去很高端,密封類,實(shí)際上并不難理解,它密封的是邏輯,作用就是可以讓邏輯更加完善、嚴(yán)謹(jǐn)。

          舉個(gè)很常見的例子,在網(wǎng)絡(luò)請求中有兩種狀態(tài),Success和Fail。

          open?class?Result

          class?Success(val?msg:?String)?:?Result()
          class?Fail(val?error:?Throwable)?:?Result()

          fun?getResult(result:?Result)?=?when?(result)?{
          ????is?Success?->?result.msg
          ????is?Fail?->?result.error.message
          ????else?->?throw?IllegalArgumentException()
          }

          在判斷的時(shí)候,可以使用when來進(jìn)行判斷,但是必須有else條件,這就導(dǎo)致了網(wǎng)絡(luò)請求的狀態(tài)出來三種狀態(tài),即Success、Fail和else,這樣一不利于邏輯的完整性,也容易在狀態(tài)很多的時(shí)候漏掉一些狀態(tài)的判斷。

          所以,Kotlin提供了Sealed Class來解決這個(gè)問題,避免使用when的時(shí)候,出現(xiàn)這種無用的判斷分支。代碼如下所示。

          sealed?class?Results
          class?Success(val?message:?String)?:?Results()
          class?Failure(val?error:?Exception)?:?Results()

          fun?getMessage(result:?Results)?=?when?(result)?{
          ????is?Success?->?println(result.message)
          ????is?Failure?->?println(result.error.toString())
          }

          這樣可以在when的時(shí)候通過快捷鍵自動(dòng)羅列所有的場景。

          更加復(fù)雜的,還可以使用Sealed Class來創(chuàng)建嵌套的密封邏輯,例如前面的Error中,還可以封裝更為詳細(xì)的Error類型,在這樣的場景下,Sealed Class的優(yōu)勢就能更一步體現(xiàn)出來了,代碼如下所示。

          sealed?class?Result?{
          ????data?class?Success(val?message:?String)?:?Result()
          ????sealed?class?Error(val?error:?Exception)?:?Result()?{
          ????????class?SystemError(exception:?Exception)?:?Error(exception)
          ????????class?AuthError(exception:?Exception)?:?Error(exception)
          ????}

          ????object?NoResponse?:?Result()
          }

          fun?getMessage(result:?Result)?=?when?(result)?{
          ????is?Result.Success?->?println(result.message)
          ????is?Result.Error.SystemError?->?println(result.error)
          ????is?Result.Error.AuthError?->?println(result.error)
          ????Result.NoResponse?->?println(result)
          }

          在寫了when函數(shù)之后,只要判斷的條件是一個(gè)Sealed Class,那么都可以通過快捷鍵自動(dòng)補(bǔ)全,生成所有的枚舉條件,這可比你自己去列舉靠譜多了,特別是像這種嵌套的情況。

          在Android中,除了網(wǎng)絡(luò)請求這種比較常用的場景外,View的點(diǎn)擊的封裝,也是比較常用的例子。

          例如一個(gè)RecyclerView Item的點(diǎn)擊事件,可以封裝一個(gè)ItemClick的Sealed Class,這個(gè)類中密封了ShareClick,F(xiàn)avoriteClick,DelClick等邏輯,通過設(shè)置點(diǎn)擊監(jiān)聽,handle不同的點(diǎn)擊事件。

          Sealed Class的核心就是,用一組清晰明確的類型,將結(jié)果分配給每個(gè)密封狀態(tài),在保存邏輯的嚴(yán)謹(jǐn)性的同時(shí),減少垃圾代碼的產(chǎn)生。

          操作符重載

          操作符重載可以讓開發(fā)者在原本沒有操作符功能的函數(shù)中,為其新增操作符含義的功能。

          操作符重載是各種騷操作的來源,更是一些別有用心者的萬惡之源

          例如官方給出的例子,利用 plus (+) 和 minus (-) 對Map集合做加減運(yùn)算,如圖所示。

          代碼如下所示。

          fun?main()?{
          ????val?numbersMap?=?mapOf("one"?to?1,?"two"?to?2,?"three"?to?3)

          ????//?plus?(+)
          ????println(numbersMap?+?Pair("four",?4))?//?{one=1,?two=2,?three=3,?four=4}
          ????println(numbersMap?+?Pair("one",?10))?//?{one=10,?two=2,?three=3}
          ????println(numbersMap?+?Pair("five",?5)?+?Pair("one",?11))?//?{one=11,?two=2,?three=3,?five=5}

          ????//?minus?(-)
          ????println(numbersMap?-?"one")?//?{two=2,?three=3}
          ????println(numbersMap?-?listOf("two",?"four"))?//?{one=1,?three=3}
          }

          集合中本沒有「+」、「-」操作,但是可以通過重載操作符,給集合類型的變量增加這樣的功能,這樣寫起來更加方便,除了常見的「+」、「-」操作以外,下面這些操作符都可以被重載。

          那么重載操作符到底是怎么實(shí)現(xiàn)的呢?Java中好像并沒有這種功能,所以,Kotlin一定是通過編譯器的黑魔法來實(shí)現(xiàn)的,通過反編譯Kotlin的代碼,可以發(fā)現(xiàn)這個(gè)黑魔法。例如上面Map的plus重載運(yùn)算符,在反編譯之后的代碼如下所示。

          很明顯,Kotlin就是在編譯的時(shí)候,把重載的操作符替換成了前面定義的函數(shù),實(shí)際上有點(diǎn)類似拓展函數(shù)的實(shí)現(xiàn),所以Java其實(shí)本身不支持重載操作符,但是Kotlin通過編譯器來實(shí)現(xiàn)了操作符的重載。

          拓展in的操作符

          in操作符具有很強(qiáng)的語義性,所以在自定義的類中,重載in操作符,可以簡化很多操作,特別是在when條件判斷中,例如在Collection中,Kotlin就重載了in操作符,提供了更加方便的判斷,代碼如下所示。

          fun?main()?{
          ????when?(val?input?=?"xuyisheng")?{
          ????????in?listOf("xuyisheng",?"zhujia")?->?println("result?$input")
          ????????in?setOf("zj",?"rkk")?->?println("result?$input")
          ????????else?->?println("result?not?found")
          ????}
          }

          那么我們可以模仿Kotlin官方的做法,在自定義的類中重載in操作符,例如給正則增加in操作符,用來判斷匹配類型,代碼如下所示。

          operator?fun?Regex.contains(text:?CharSequence):?Boolean?{
          ????return?this.containsMatchIn(text)
          }

          fun?main()?{
          ????when?(val?input?=?"abc")?{
          ????????in?Regex("[0–9]")?->?println("contains?a?number")
          ????????in?Regex("[a-zA-Z]")?->?println("contains?a?letter")
          ????}
          }

          通過這種方式,語義更加明確,代碼也更加簡潔。

          操作符重載一定要慎用,防止有些人重載「+」為「-」,導(dǎo)致代碼難以理解。

          集合操作

          在Kotlin中,集合有兩種類型,即Collection和Sequence,在Java中,我們很少提及有兩種集合類型,以至于在寫Kotlin的時(shí)候,對它提供的這兩種集合類型傻傻分不清楚。但在Kotlin的函數(shù)式編程世界里,它們的區(qū)別是非常大的。

          立即執(zhí)行 (eagerly) 的Collection類型

          Collection,是我們最長用的集合類型,甚至成了集合的代名詞,它的特點(diǎn)如下。

          • 每次操作時(shí)立即執(zhí)行的,執(zhí)行結(jié)果會(huì)被存儲(chǔ)到一個(gè)新的集合中
          • Collection中的轉(zhuǎn)換操作是內(nèi)聯(lián)函數(shù)。例如map函數(shù)的實(shí)現(xiàn)方式,它是一個(gè)創(chuàng)建了新ArrayList的內(nèi)聯(lián)函數(shù),如下圖所示。

          這也是通常在使用Collection的函數(shù)式編程方式時(shí),內(nèi)存使用更大的原因。

          延遲執(zhí)行 (lazily) 的Sequence類型

          Sequence,也是集合的一種,但是被Collection搶了翻譯,所以只能叫做序列,它跟Collection最大的區(qū)別就是,Sequence是延遲執(zhí)行的。

          它有兩種類型: 中間操作 (intermediate) 和末端操作 (terminal)。中間操作不會(huì)立即執(zhí)行,它們只是被存儲(chǔ)起來,僅當(dāng)末端操作被調(diào)用時(shí),才會(huì)按照順序在每個(gè)元素上執(zhí)行中間操作,然后執(zhí)行末端操作。

          中間操作 (比如 map、distinct、groupBy 等) 會(huì)返回另一個(gè)Sequence,而末端操作 (比如 first、toList、count 等) 則不會(huì)。

          同樣是map函數(shù),在Sequence中,像map這樣的中間操作是將轉(zhuǎn)換函數(shù)會(huì)存儲(chǔ)在一個(gè)新的Sequence實(shí)例中,如圖所示。

          而例如first這樣的末端操作,則會(huì)真正執(zhí)行具體的操作。例如first,則會(huì)對Sequence中的元素進(jìn)行遍歷,直到找到預(yù)置條件匹配為止,代碼執(zhí)行如下所示。

          下面通過一個(gè)例子來演示下這兩種集合類型的操作異同。

          data?class?People(val?name:?String,?val?age:?Int)

          val?xuyisheng?=?People("xuyisheng",?18)
          val?zhujia?=?People("zhujia",?3)
          val?rkk?=?People("rkk",?28)
          val?zj?=?People("zj",?38)

          val?list?=?listOf(xuyisheng,?zhujia,?rkk,?zj)

          fun?main()?{
          ????val?testCollection?=?list.map?{
          ????????it.copy(age?=?1)
          ????}.first?{
          ????????it.name?==?"xuyisheng"
          ????}
          ????println(testCollection)

          ????val?testSequence?=?list.asSequence().map?{
          ????????it.copy(age?=?1)
          ????}.first?{
          ????????it.name?==?"xuyisheng"
          ????}
          ????println(testSequence)
          }

          首先,我創(chuàng)建了一個(gè)List,默認(rèn)為Collection類型,通過asSequence函數(shù),可以將其轉(zhuǎn)換為Sequence。下面分別針對這兩種方式來看下具體的代碼執(zhí)行的流程。

          Collections執(zhí)行過程

          1. 調(diào)用map函數(shù)時(shí)會(huì)創(chuàng)建一個(gè)新的ArrayList。Kotlin會(huì)遍歷初始Collection中所有項(xiàng)目,并復(fù)制原始的對象,并將每個(gè)元素的age值改為1,再將其添加到新創(chuàng)建的列表中。

          2. 調(diào)用first函數(shù)時(shí),會(huì)遍歷每一個(gè)元素,直到找到第一個(gè)符合條件的元素。

          Sequences執(zhí)行過程

          1. 調(diào)用asSequence函數(shù)創(chuàng)建一個(gè)基于原始集合的迭代器創(chuàng)建一個(gè)Sequence。
          2. 調(diào)用map函數(shù),這是一個(gè)中間操作,所以Sequence會(huì)將轉(zhuǎn)換操作的信息存儲(chǔ)到一個(gè)列表中,該列表只會(huì)存儲(chǔ)要執(zhí)行的操作,但并不會(huì)執(zhí)行這些操作。
          3. 調(diào)用first函數(shù)時(shí),這是一個(gè)末端操作,所以它會(huì)將中間操作作用到集合中的每個(gè)元素。我們遍歷初始集合和之前存儲(chǔ)的操作列表,對每個(gè)元素執(zhí)行map操作,然后繼續(xù)執(zhí)行first操作,當(dāng)遍歷到符合條件的數(shù)據(jù)時(shí),就完成了操作,所以就無需在剩余的元素中進(jìn)行map操作了。

          綜上所述,它們的差異如下。

          • 使用Sequence是不會(huì)去創(chuàng)建中間集合的,但會(huì)創(chuàng)建中間操作集合,在執(zhí)行末端操作時(shí),由于Item會(huì)被逐個(gè)執(zhí)行,所以中間操作只會(huì)作用到部分Item上。

          • Sequence每個(gè)元素被依次驗(yàn)證,Collection每個(gè)操作都將作用在整個(gè)集合,每個(gè)操作都將創(chuàng)建新的集合。

          • Collection會(huì)為每個(gè)轉(zhuǎn)換操作創(chuàng)建一個(gè)新的集合,而Sequence僅僅是保留對轉(zhuǎn)換函數(shù)的引用。

          Collection的操作使用了內(nèi)聯(lián)函數(shù),所以處理所用到的字節(jié)碼以及傳遞給它的lambda字節(jié)碼都會(huì)進(jìn)行內(nèi)聯(lián)操作。而Sequence不使用內(nèi)聯(lián)函數(shù),因此,它會(huì)為每個(gè)操作創(chuàng)建新的Function對象。

          使用場景

          針對Collection和Sequence的這種差異,我們需要在不同的場景下,選擇不同的集合類型。

          • 數(shù)據(jù)量小的時(shí)候,其實(shí)Collection和Sequence的使用并無差異
          • 數(shù)據(jù)量大的時(shí)候,由于Collection的操作會(huì)不斷創(chuàng)建中間態(tài),所以會(huì)消耗過多資源,這時(shí)候,就需要采用Sequence了
          • 對集合的函數(shù)式操作太大,例如需要對集合做map、filter、find等等操作,同樣是使用Sequence更高效


          瀏覽 66
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜夜嗨AⅤ一区二区三区 | 日本中文字幕在线 | 黑人久久久久 | 超碰免费青娱乐 | 污污成人免费网站 |