<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【從零開始擼一個(gè)App】Kotlin

      工欲善其事必先利其器。像我們從零開始擼一個(gè)App的話,選擇最合適的語言是首要任務(wù)。如果你跟我一樣對(duì)Java蹣跚的步態(tài)和僵硬的語法頗感無奈,那么Kotlin在很大程度上不會(huì)令你失望。雖然為了符合JVM規(guī)范和兼容Java,它引入了一些較為復(fù)雜的概念和語法,很多同學(xué)就是因此放棄入門。其實(shí)越深入進(jìn)去,就會(huì)越欲罷不能。除了Android開發(fā),博主也常在后端使用Kotlin編碼,有時(shí)因?yàn)槟承┰蛲瑫r(shí)使用Java混編。總的來說,能減少代碼量,提高生產(chǎn)效率,似乎代碼結(jié)構(gòu)也更清晰了。如果你沒有Kotlin的經(jīng)驗(yàn),但是比較過Java和C#,你就明白我的意思了,甚至Kotlin有些地方比C#還方便。可以說Kotlin既有C#便捷的語法,亦背靠Java平臺(tái)良好的生態(tài),那么你還在猶豫什么?

      基礎(chǔ)

      var:可變,是一個(gè)可變變量。可知var類型屬性不能設(shè)置為延遲加載屬性,因?yàn)樵?code>lazy中并沒有setValue(…)方法。在DI場(chǎng)景下,常與lateinit搭配使用,可參看Kotlin中l(wèi)ateinit變量在字節(jié)碼層面上的解釋
      val:不可變,一個(gè)只讀變量。另外還有const val,只允許在top-level級(jí)別和object中使用。它們的區(qū)別如下:

      • const val 可見性為public final static,可以直接訪問。
      • val 可見性為private final static,并且val 會(huì)生成方法getNormalObject(),通過方法調(diào)用訪問。

      Unit:當(dāng)一個(gè)函數(shù)沒有返回值的時(shí)候,我們用Unit來表示這個(gè)特征,同Java中的void。

      open:在java中允許創(chuàng)建任意的子類并重寫方法任意的方法,除非顯示的使用了final關(guān)鍵字進(jìn)行標(biāo)注。而在Kotlin的世界里面則不是這樣,在Kotlin中它所有的類默認(rèn)都是final的,那么就意味著不能被繼承,而且在類中所有的方法也是默認(rèn)是final的,那么就是Kotlin的方法默認(rèn)也不能被重寫。為類增加open,class就可以被繼承了;為方法增加open,那么方法就可以被重寫了。

      inlineKotlin 內(nèi)聯(lián)函數(shù) inline。它會(huì)將代碼塊拷貝到調(diào)用的地方,減少了調(diào)用層數(shù)和額外對(duì)象的產(chǎn)生。
      crossinline:這是因inline的副作用而引入的關(guān)鍵字。由于inline會(huì)將代碼拷貝到調(diào)用的地方,如果代碼里面有return,那么目標(biāo)代碼(調(diào)用者)的邏輯可能就被破壞了。用crossinline修飾相應(yīng)的lambda,將return返回到對(duì)應(yīng)標(biāo)簽[,而不是返回到整個(gè)方法]。
      reified:為了應(yīng)對(duì)Java偽泛型導(dǎo)致的代碼冗余問題。可參看使用Kotlin Reified 讓泛型更簡(jiǎn)單安全。這主要是應(yīng)對(duì)Java中的泛型擦除。Java中的泛型是偽泛型,即它的泛型只存在于編譯期,在生成的字節(jié)碼文件中是不包含任何泛型信息的(不過至少在編譯期就能及早發(fā)現(xiàn)類型不匹配的問題),在編譯后的字節(jié)碼文件中,就已經(jīng)被替換為原來的原始類型(Raw Type/Object)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,是為類型擦除。因此對(duì)于運(yùn)行期的Java語言來說,ArrayList<int>與ArrayList<String>就是同一個(gè)類型。相對(duì)的,C#中使用的泛型,就是真泛型,其泛型無論在程序源碼中、編譯后的IL中或是運(yùn)行期的CLR中都是切實(shí)存在的,List<int>與List<String>就是兩個(gè)不同的類型,它們有自己的虛方法表和類型數(shù)據(jù)。
      下面是我封裝RabbitMQ消費(fèi)端監(jiān)聽的代碼(感興趣的同學(xué)可以參看本人博文RabbitMQ入門指南獲取更多信息):

          /**
           * 從指定隊(duì)列獲取消息,并定義回調(diào)(for kotlin)
           *
           * @param queue the name of the queue from where receive messages
           * @param block callback when a message arrived
           */
          inline fun <reified T> receive(queue: String, crossinline block: (T) -> Boolean) {
              factory.newConnection().use {
                  val conn = it.get()
                  val channel = conn.createChannel()
                  channel.basicConsume(queue, false, object : DefaultConsumer(channel) {
                      override fun handleDelivery(consumerTag: String?, envelope: Envelope, properties: AMQP.BasicProperties?, body: ByteArray) {
                          try {
                              val message = JSON.parseObject(String(body), object : TypeReference<T>() {})
                              val done = block(message)
                              if (done) {
                                  channel.basicAck(envelope.deliveryTag, false)
                              } else {
                                  //若失敗則重新投遞一次,否則丟棄或投遞到死信隊(duì)列(若配置了的話)
                                  channel.basicNack(envelope.deliveryTag, false, !envelope.isRedeliver)
                              }
                          } catch (e: Exception) {
                              _logger.error("處理消息-${String(body)}時(shí)發(fā)生錯(cuò)誤-${e.message}")
                              throw e
                          }
                      }
                  })
              }
          }
      

      注意Java編譯器不支持inlinereified等關(guān)鍵字,所以如果要使用Java調(diào)用,還需要另外寫for java的版本。

      field:用于屬性取值/賦值邏輯(如果顯式定義的話),類似于C#屬性中的value關(guān)鍵字,防止訪問器的自遞歸而導(dǎo)致程序崩潰的 StackOverflowError異常,參看kotlin學(xué)習(xí)—Field

      this@ClassName:匿名內(nèi)部類對(duì)象引用[包含它的]外部對(duì)象。

      by:修飾屬性和字段,提供若干效用,可參看Kotlin by
      在監(jiān)聽值更改的場(chǎng)景中,by 的作用類似于 C# 屬性的 set 方法。當(dāng)然, kotlin 自己也有 get/set 語法。
      更進(jìn)一步,如果一個(gè)對(duì)象有 getValue() [和 setValue()] 方法,則這個(gè)對(duì)象就可以作為 by 后面的表達(dá)式。
      還可以在類定義時(shí)使用,可以將某實(shí)例的所有的 public 方法委托該類[,似乎這些方法就是在這個(gè)類中定義的]。這應(yīng)該是組合的形態(tài),但我們也可用它實(shí)現(xiàn)某種語法程度的“多繼承”,以后面協(xié)程部分的代碼片段為例:

      class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}
      

      其中CoroutineScope是interface,MainScope()返回的是CoroutineScope的實(shí)現(xiàn)類ContextScope實(shí)例。也就是說,BasicCorotineActivity實(shí)現(xiàn)了接口CoroutineScope,但BasicCorotineActivity本身不實(shí)現(xiàn)其中的方法,而是委托給MainScope()返回的對(duì)象幫它實(shí)現(xiàn)。這減少了代碼冗余,從寫法上看,也似乎BasicCorotineActivity同時(shí)繼承了AppCompatActivity類和CoroutineScope實(shí)例:)

      在kotlin中interface不僅可以聲明函數(shù),還可以對(duì)函數(shù)進(jìn)行實(shí)現(xiàn)。與類唯一不同的是它們是無狀態(tài)的,所以屬性需要子類去重寫。類需要去負(fù)責(zé)保存接口屬性的狀態(tài)。

      Elvis操作符:?: ,類似js中的 | ,若前者為null則取后者。

      Kotlin并非一門純粹的語言,它在語法部分常考慮到Java的兼容和可轉(zhuǎn)換性,為此增添了不少讓新手困惑的語法和關(guān)鍵字。如對(duì)一個(gè)屬性或一個(gè)主構(gòu)造器的參數(shù)進(jìn)行注解時(shí),Kotlin元素將會(huì)生成對(duì)應(yīng)的多個(gè)Java元素,因此在Java字節(jié)碼中該注解有多個(gè)可能位置。如果要精確指定該如何生成該注解,可使用以下語法:

      class Example(@field:Ann val foo,    // annotate Java field
                    @get:Ann val bar,      // annotate Java getter
                    @param:Ann val quux)   // annotate Java constructor parameter
      

      更多可參看Kotlin編碼竅門之注解(Annotations)

      companion objectobject:Kotlin 移除了 static 的概念,這兩者轉(zhuǎn)換成Java后都有靜態(tài)單例的模式,容易讓人困惑它們的區(qū)別。其實(shí)從使用場(chǎng)景分析就比較明了了,前者作為一個(gè)類的靜態(tài)內(nèi)部單例類[對(duì)象]使用(companion就是伴侶的意思),后者就是一個(gè)靜態(tài)單例類[對(duì)象],不需要外圍類的存在(沒有companion嘛)。
      在companion object場(chǎng)景下我們常使用@JvmStatic@JvmField以便將它們修飾的方法和字段[在外部Java代碼看來]暴露為類的子級(jí),可參看微知識(shí)#1 Kotlin 的 @JvmStatic 和 @JvmField 注解
      相關(guān)概念:@JvmOverloads
      object關(guān)鍵字還可用于創(chuàng)建接口或者抽象類的匿名對(duì)象。

      Kotlin允許你在文件中定義頂級(jí)的函數(shù)和屬性。

      Kotlin除了有擴(kuò)展方法,還有擴(kuò)展屬性,參看Kotlin的擴(kuò)展屬性和擴(kuò)展方法

      Kotlin的函數(shù)參數(shù)是只讀的。

      重溫一下表達(dá)式與語句的區(qū)別。表達(dá)式有值,并能作為另一個(gè)表達(dá)式的一部分來使用;而語句沒有返回值。Java 中的控制結(jié)構(gòu)皆為語句。而在 Kotlin 中,除了循環(huán)體結(jié)構(gòu)外,大多數(shù)控制結(jié)構(gòu)都是表達(dá)式。


      lambda

      Kotlin中的語法糖特別的多,比如lambda表達(dá)式,作為參數(shù)傳遞就有幾種不同的寫法:

      1. 普通方式:button.setOnClickListener({strInfo: String -> Unit})
      2. 如果最后一個(gè)參數(shù)是傳遞的lambda表達(dá)式,可以在圓括號(hào)之外指定:button.setOnClickListener(){strInfo: String -> Unit}
      3. 如果函數(shù)的參數(shù)只有一個(gè)[或者其它參數(shù)都有默認(rèn)值],并且這個(gè)參數(shù)是lambda,就可以省略圓括號(hào):button.setOnClickListener{strInfo: String -> Unit}
      4. 甚至可以省略為:button.setOnClickListener{strInfo}

      以上面例子為例,如果setOnClickListener接受的參數(shù)不是lambda類型而是一個(gè)interface,該interface下只有一個(gè)方法,那么同樣可以使用上述語法[,似乎setOnClickListener接受的參數(shù)就是lambda類型]。此類interface常用@FunctionalInterface修飾。(其實(shí)這應(yīng)該就是java的特性,如RxJava中的subscribe(Consumer<? super T> onNext),在別人調(diào)用它的時(shí)候就可以直接傳lambda表達(dá)式)。
      注意,用kotlin自己寫的interface并不支持此特性

      在調(diào)用時(shí)將lambda方法體移至括號(hào)外面應(yīng)該是為了代碼的可讀性,使得更貼近代碼邏輯塊而非單個(gè)參數(shù)的感覺。


      協(xié)程Coroutine

      首先我們要知道一點(diǎn),協(xié)程這個(gè)概念現(xiàn)在有點(diǎn)被濫用了,市面上流行的語言似乎都想把協(xié)程納入自己的特性里。如果你對(duì)協(xié)程還不了解,請(qǐng)參看博主寫的再談協(xié)程或其它資料。博主認(rèn)為真正的協(xié)程是如Go那樣的實(shí)現(xiàn)。Kotlin雖然也有協(xié)程,但更類似于C#里的async/await,是在多線程層面的語法處理。更深入的分析可參看Kotlin 協(xié)程真的比 Java 線程更高效嗎?

      suspend:關(guān)鍵字,它一般標(biāo)識(shí)在一個(gè)函數(shù)的開頭,用于表示該函數(shù)是個(gè)耗時(shí)操作。這個(gè)關(guān)鍵字主要作用就是為了作一個(gè)提醒,并不會(huì)因?yàn)樘砑恿诉@個(gè)關(guān)鍵字就會(huì)該函數(shù)立即跑到一個(gè)子線程上。是否切換線程仍是由launchwithContextasync決定的。當(dāng)然了,有時(shí)候我們必須在函數(shù)前面加上suspend,如果函數(shù)內(nèi)部調(diào)用了其它suspend函數(shù)的話。

      如果使用retrofit2封裝網(wǎng)絡(luò)請(qǐng)求的話,接口定義,原本每個(gè)函數(shù)應(yīng)該返回的是Call<>(若有返回的話)類型。或者可以使用Jake Wharton寫的CoroutineCallAdapterFactory組件,它使得函數(shù)支持Deferred<>返回值,簡(jiǎn)化協(xié)程+retrofit2的開發(fā)。不過從Retrofit 2.6.0起,Retrofit內(nèi)置了對(duì)suspend關(guān)鍵字的支持,可以以更純粹的方式定義函數(shù),如:

      @GET("users/{id}")
      suspend fun user(@Path("id") id: Long): User
      

      若要將傳統(tǒng)的回調(diào)封裝成協(xié)程模式,可使用suspendCoroutinesuspendCancellableCoroutine,如下所示:

          suspend fun MqttAsyncClient.aPublish(payload: MqttMessage, topic: String): IMqttToken =
              suspendCoroutine { cont ->
                  publish(topic, payload, null, object: MqttActionListener {
                      override fun onSuccess(asyncActionToken: IMqttToken) {
                          cont.resume(asyncActionToken)
                      }
      
                      override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable) {
                          cont.resumeWithException(exception)
                      }
                  })
              }
      

      盡可能使用suspendCancellableCoroutine而不是suspendCoroutine,因?yàn)槭褂们罢邉t協(xié)程的取消是可控的。Kotlin沒有檢查異常,但是我們?nèi)匀恍枰趖ry-catch中處理所有可能的異常。否則,該應(yīng)用程序?qū)⒈罎ⅰ5莝uspendCancellableCoroutine取消拋出的異常CancellationException是個(gè)意外,它并不會(huì)導(dǎo)致程序崩潰。

      慣常用CoroutineScope.launch創(chuàng)建協(xié)程(當(dāng)然還有runBlockingwithContextasync等),它會(huì)返回一個(gè)Job對(duì)象,便于在外部對(duì)協(xié)程進(jìn)行控制。

      • job.join():阻塞當(dāng)前線程,直到j(luò)ob執(zhí)行完畢。這是一個(gè) suspend 函數(shù),所以一般在 Coroutine 內(nèi)調(diào)用,阻塞當(dāng)前所在Coroutine。
      • job.cancel():取消job,執(zhí)行后該job就進(jìn)入cancelling狀態(tài),但是否真的取消了需要看job自身實(shí)現(xiàn)。Coroutine標(biāo)準(zhǔn)庫中定義的 suspend function 都是支持取消操作的(比如 delay)。自定義job的時(shí)候可以通過 isActive 屬性來判斷當(dāng)前任務(wù)是否被取消了,如果發(fā)現(xiàn)被取消了則停止繼續(xù)執(zhí)行。如果自定義job沒有相應(yīng)的處理邏輯,那么就算調(diào)用job.cancel(),也并不能取消它的執(zhí)行。
      • SupervisorJob(parent: Job? = null):返回一個(gè)job實(shí)例,里面的子Job不相互影響,一個(gè)子Job失敗了,不影響其他子Job的執(zhí)行。parent參數(shù)用于關(guān)聯(lián)自己本身的父job。如果研究協(xié)程源碼的話,會(huì)常看到ContextScope(SupervisorJob() + Dispatchers.Main)的寫法(如ViewModel.viewModelScope的實(shí)現(xiàn)),這里的 + 號(hào)是CoroutineContext對(duì)操作符plus的重載,前后兩者都是CoroutineContext的子類。

      Dispatchers

      • Dispatchers.Main 調(diào)用程序在Android的主線程中
      • Dispatchers.IO 適合主線程之外的執(zhí)行磁盤或者網(wǎng)絡(luò)io操作,例如文件的讀取與寫入,任何的網(wǎng)絡(luò)請(qǐng)求
      • Dispatcher.Default 適合主線程之外的,cpu的操作,例如json數(shù)據(jù)的解析,以及列表的排序

      注意,By default, the maximum number of threads used by this dispatcher is equal to the number of CPU cores, but is at least two. Dispatchers.IO的調(diào)度/執(zhí)行線程同Dispatcher.Default一樣,它們使用同一個(gè)線程池,但是遇到IO操作,Dispatchers.IO會(huì)另外創(chuàng)建線程用于處理IO過程(而Dispatcher.Default不會(huì),也就是同一個(gè)線程即干計(jì)算的活,也干搬運(yùn)的活)。
      Dispatchers.IO能創(chuàng)建的線程數(shù):The number of threads used by tasks in this dispatcher is limited by the value of “kotlinx.coroutines.io.parallelism” (IO_PARALLELISM_PROPERTY_NAME) system property. It defaults to the limit of 64 threads or the number of cores (whichever is larger).


      后記拾遺

      當(dāng)使用公有屬性時(shí),有時(shí)會(huì)拋出“Smartcast is impossible because propery has open or custom getter”的編譯時(shí)錯(cuò)誤,究其原因是編譯器分析代碼發(fā)現(xiàn)每次get屬性時(shí)返回的對(duì)象可能不是同一個(gè)。解決方法很簡(jiǎn)單,只要定義一個(gè)臨時(shí)變量指向某次get獲得的值即可。可參看Smartcast is impossible because propery has open or custom getter

      Java泛型擦除導(dǎo)致的問題。如下代碼可正常運(yùn)行:

          private fun  getToken(): Token? {
              val preference = TaoismApplication.appContext.sharedPreferences(SharedPreference.SESSION)
              val json = preference.getString(SharedPreference.TOKEN, "")
              if (!json.isNullOrBlank()) {
                  return Gson().fromJson<Token>(json, object : TypeToken<Token>() {}.type)
              } else {
                  return null
              }
          }
      

      由于代碼中有較多getXXX(),抽取模板代碼:

          private fun <T : Any?> get(key: String): T? {
              val preference = TaoismApplication.appContext.sharedPreferences(SharedPreference.SESSION)
              val json = preference.getString(key, "")
              if (!json.isNullOrBlank()) {
                  return Gson().fromJson<T>(json, object : TypeToken<T>() {}.type)
              } else {
                  return null
              }
          }
      

      調(diào)用get<Token>(SharedPreference.TOKEN)報(bào)錯(cuò):java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.xxx.xxx.Token
      so,只能將類型信息顯式傳入,改造方法簽名為get(key: String, typeToken: Type)

      kotlin異常:Kotlin 的異常都是 Unchecked exception。若在函數(shù)上注解了@Throws,則編譯成Java代碼會(huì)變成符合Java模式的checked exception,即在方法定義上會(huì)顯式聲明可能拋出的異常類型,需要在調(diào)用鏈路上處理。對(duì)于Kotlin自身來說@Throws并沒有太多意義,it is only for Java developer to know that they need to handle that exception.參看[Kotlin] Try catch & Throw

      使用intellij idea進(jìn)行kotlin和java混合開發(fā),最好將kotlin文件和java文件分各自文件夾存放,否則運(yùn)行時(shí)可能會(huì)報(bào)找不到類的錯(cuò)誤(因?yàn)榫幾g時(shí)會(huì)將不是屬于該文件夾的且沒有被其它文件引用的代碼文件忽略)。如下:


      參考資料

      Kotlin中標(biāo)準(zhǔn)函數(shù)run、with、let、also與apply的使用和區(qū)別詳解
      Kotlin協(xié)程 —— 今天說說 launch 與 async
      Kotlin之美——DSL篇

      posted @ 2020-10-10 15:32  萊布尼茨  閱讀(2110)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 日韩丝袜亚洲国产欧美一区 | 日韩av综合免费在线| 久久精品中文字幕有码| 国产精品一区二区传媒蜜臀| 精品国产免费一区二区三区香蕉| 久久精品久久电影免费理论片| 亚洲人成小说网站色在线| 精品国产美女福到在线不卡| 你拍自拍亚洲一区二区三区| 2021亚洲va在线va天堂va国产| 天天躁日日躁狠狠躁中文字幕| 国产在线视频一区二区三区| 国产av精品一区二区三区| 2021亚洲国产精品无码| 久久国产成人av蜜臀| 国产精品嫩草99av在线| 一本大道无码av天堂| 在线a亚洲老鸭窝天堂| 色欲久久久天天天综合网| 日韩一区日韩二区日韩三区| 国产91午夜福利精品| 94人妻少妇偷人精品| 999国产精品999久久久久久| 亚洲真人无码永久在线| 亚洲欧美中文字幕日韩一区二区| 日韩精品成人一区二区三区| 99久久免费精品国产色| 国产精品一码二码三码| 亚洲午夜爱爱香蕉片| 一本色道久久加勒比综合 | 毛片在线看免费| 大尺度国产一区二区视频| 白丝乳交内射一二三区| 一区二区三区四区五区自拍| 亚洲国产午夜精品理论片妓女 | 亚洲精品中文字幕尤物综合| 中文字幕在线亚洲精品| 女人裸体性做爰视频| 在线精品国产中文字幕| 久久精品国产99国产精品澳门| 亚洲 欧美 影音先锋|