Jetbrains開發者見聞(三)之Kotlin1.3新特性(inline class篇)

      網友投稿 912 2025-03-31

      簡述:

      上接上篇文章,我們深入分析了Kotlin1.3版本中的Contract契約的內容,那么這篇文章將會繼續把Kotlin1.3新特性研究完畢。這篇文章還有個非常重要的點就是inline class 內聯類。關于內聯類的知識除了這篇文章會有介紹,后面馬上會翻譯幾篇有關Kotlin中的內聯類相關內容。只有一個目的徹底搞定Kotlin中的內聯類。那我們一起來看下本次提綱:

      一、inline class內聯類(Experimental)

      1、復習inline內聯函數的作用

      關于inline內聯相信大家都不陌生吧,在實際開發中我們經常會使用Kotlin中的inline內聯函數。那大家還記得inline內聯作用嗎? 這里再次復習下inline內聯的作用:

      inline內聯函數主要有兩大作用:

      用于lambda表達式調用,降低Function系列對象實例創建的內存開銷,從而提高性能。聲明成內聯函數的話,而是在調用的時把調用的方法給替換掉,可以降低很大的性能開銷。

      簡述:

      上接上篇文章,我們深入分析了Kotlin1.3版本中的Contract契約的內容,那么這篇文章將會繼續把Kotlin1.3新特性研究完畢。這篇文章還有個非常重要的點就是inline class 內聯類。關于內聯類的知識除了這篇文章會有介紹,后面馬上會翻譯幾篇有關Kotlin中的內聯類相關內容。只有一個目的徹底搞定Kotlin中的內聯類。那我們一起來看下本次提綱:

      一、inline class內聯類(Experimental)

      關于inline內聯相信大家都不陌生吧,在實際開發中我們經常會使用Kotlin中的inline內聯函數。那大家還記得inline內聯作用嗎? 這里再次復習下inline內聯的作用:

      inline內聯函數主要有兩大作用:

      用于lambda表達式調用,降低Function系列對象實例創建的內存開銷,從而提高性能。聲明成內聯函數的話,而是在調用的時把調用的方法給替換掉,可以降低很大的性能開銷。

      另一個內聯函數作用就是它能是泛型函數類型實參進行實化,在運行時能拿到類型實參的信息。

      通過復習了inline函數的兩大作用,實際上內聯類存在意義和inline函數第一個作用有點像。有時候業務場景需要針對某種類型創建包裝器類。 但是,使用包裝器類避免不了去實例化這個包裝器類,但是這樣會帶來額外的創建對象的堆分配,它會引入運行時開銷。 此外,如果包裝類型是基礎類型的,性能損失是很糟糕的,因為基礎類型通常在運行時大大優化,而它們的包裝器沒有得到任何特殊處理。

      那到底是什么場景會需要內聯類呢? 實際上,在開發中有時候就基本數據類型外加變量名是無法完全表達某個字段的含義,甚至還有可能造成歧義,這種場景就特別適合內聯類包裝器。是不是很抽象,那么一起來看個例子。

      案例一: Iterable擴展函數joinToString源碼分析

      public?fun??Iterable.joinToString(separator:?CharSequence?=?",?",?prefix:?CharSequence?=?"",?postfix:?CharSequence?=?"",?limit:?Int?=?-1,?truncated:?CharSequence?=?"...",?transform:?((T)?->?CharSequence)??=?null):?String?{

      return?joinTo(StringBuilder(),?separator,?prefix,?postfix,?limit,?truncated,?transform).toString()

      }

      我們仔細分析下joinToString函數,它有很多個函數參數,其中讓人感到歧義就是前面三個: separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "".有的人就會說不會有歧義啊,定義得很清楚很明白了,separator,prefix,postfix形參名明顯都有自己的含義啊。僅僅從joinToString這個函數的角度來看確實比較清晰。可是你有沒有想過外層調用者困惑呢。對于外部調用者前面三個參數都是CharSequence類型,對于他們而言除非去看函數聲明然后才知道每個參數代表什么意思,外面調用者很容易把三個參數調用順序弄混了。就像下面這樣。

      fun?main(args:?Array)?{

      val?numberList?=?listOf(1,?3,?5,?7,?9,?11,?13)

      println(numberList.joinToStr("<",",",">"))

      //這段代碼在調用者看來就僅僅是傳入三個字符串,給人看起來很迷惑,根本就不知道每個字符串實參到底代表是什么意思。

      //這里代碼是很脆弱的,在不看函數聲明時,字符串要么很容易被打亂順序,要么沒人敢改這里的代碼了。

      }

      fun??Iterable.joinToStr(separator:?CharSequence?=?",?",?prefix:?CharSequence?=?"",?postfix:?CharSequence?=?""):?String{

      return?this.joinToString(separator,?prefix,?postfix)

      }

      上面那種問題,為什么我們平時感受不到呢? 這是因為IDE幫你做了很多工作,但是試想下如果你的代碼離開IDE就突然感覺很丑陋. 就看上面那段代碼假如沒有給你joinToStr函數聲明定義,是不是對傳入三個參數一臉懵逼啊。針對上述實際上有三種解決辦法:

      第一種: IDE高亮提示

      第二種: Kotlin中命名參數

      關于Kotlin中命名參數解決問題方式和IDE提示解決思路是一樣的。關于Kotlin中命名參數不了解的可以參考我之前這篇文章淺談Kotlin語法篇之如何讓函數更好地調用(三)

      fun?main(args:?Array)?{

      val?numberList?=?listOf(1,?3,?5,?7,?9,?11,?13)

      println(numberList.joinToStr(prefix?=?"<",?separator?=?",",?postfix?=?">"))

      }

      第三種: 使用包裝器類解決方案

      fun?main(args:?Array)?{

      val?numberList?=?listOf(1,?3,?5,?7,?9,?11,?13)

      println(numberList.joinToStr(Speparator(","),?Prefix("<"),?Postfix(">")))

      }

      class?Speparator(val?separator:?CharSequence)

      class?Prefix(val?prefix:?CharSequence)

      class?Postfix(val?postfix:?CharSequence)

      fun??Iterable.joinToStr(

      separator:?Speparator,

      prefix:?Prefix,

      postfix:?Postfix

      ):?String?{

      return?this.joinToString(separator.separator,?prefix.prefix,?postfix.postfix)

      }

      看到這里是不是很多人覺得這樣實現有問題,雖然它能很好解決我們上述類型不明確的問題。但是卻引入一個更大問題,需要額外創建Speparator、Prefix、Postfix實例對象,帶來很多的內存開銷。從投入產出比來看,估計沒人這么玩吧。這是因為inline class沒出來之前,但是如果inline class能把性能開銷降低到和直接使用String一樣的性能開銷,你還認為它是很差的方案嗎? 請接著往下看

      第四種: Kotlin中inline class終極解決方案

      針對上述問題,Kotlin提出inline class解決方案,就是從源頭上解決問題。一起來看看:

      fun?main(args:?Array)?{

      val?numberList?=?listOf(1,?3,?5,?7,?9,?11,?13)

      println(numberList.joinToStr(Speparator(","),?Prefix("<"),?Postfix(">")))

      }

      //相比上一個方案,僅僅是多加了inline關鍵字

      inline?class?Speparator(val?separator:?CharSequence)

      inline?class?Prefix(val?prefix:?CharSequence)

      inline?class?Postfix(val?postfix:?CharSequence)

      fun??Iterable.joinToStr(

      separator:?Speparator,

      prefix:?Prefix,

      postfix:?Postfix

      ):?String?{

      return?this.joinToString(separator.separator,?prefix.prefix,?postfix.postfix)

      }

      通過使用inline class來改造這個案例,會發現剛剛上述那個問題就被徹底解決了,外部調用者不會再一臉懵逼了,一看就很明確,是該傳入Speparator、Prefix、Postfix對象。性能開銷問題的完全不用擔心了,它和直接使用String的性能幾乎是一樣的,這樣一來是不是覺得這種解決方案還不錯呢。至于它是如何做到的,請接著往下看。這就是為什么需要inline class場景了。

      基本定義

      inline class 為了解決包裝器類帶來額外性能開銷問題的一種特殊類。

      基本結構

      基本結構很簡單就是在普通class前面加上inline關鍵字

      因為kotlin中的inline class還是處于Experimental中,所以你要使用它需要做一些額外的配置。首先你的Kotlin Plugin升級到1.3版以上,然后配置gradle,這里給出IntelliJ IDEA和AndroidStudio嘗鮮gradle配置:

      IntelliJ IDEA中gradle配置

      compileKotlin?{

      kotlinOptions.jvmTarget?=?"1.8"

      kotlinOptions?{

      freeCompilerArgs?=?["-XXLanguage:+InlineClasses"]

      }

      }

      AndroidStudio中gradle配置

      tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all?{

      kotlinOptions?{

      jvmTarget?=?'1.8'

      freeCompilerArgs?=?['-XXLanguage:+InlineClasses']

      }

      }

      使用maven的配置

      -XXLanguage:+InlineClasses

      估計很多人都沒使用過typealias吧,如果還不了解typealias的話請參考我之前的這篇文章:[譯]有關Kotlin類型別名(typealias)你需要知道的一切. 其實上述那個問題還可以用typealias來改寫,但是你會發現是有點缺陷的,并沒有達到想要效果。可以給大家看下:

      typealias?Speparator?=?CharSequence

      typealias?Prefix?=?CharSequence

      typealias?Postfix?=?CharSequence

      fun?main(args:?Array)?{

      val?numberList?=?listOf(1,?3,?5,?7,?9,?11,?13)

      println(numberList.joinToStr(",",?"<",?">"))

      }

      fun??Iterable.joinToStr(

      separator:?Speparator,

      prefix:?Prefix,

      postfix:?Postfix

      ):?String?{

      return?this.joinToString(separator,?prefix,?postfix)

      }

      關于inline class和typealias有很大相同點不同點,相同點在于: 他們兩者看起來貌似都引入一種新類型,并且兩者都將在運行時表現為基礎類型。不同點在于: typealias僅僅是給基礎類型取了一個別名而已,而inline class是基礎類型一個包裝器類。換句話說inline class才是真正引入了一個新的類型,而typealias則沒有。

      是不是還是有點抽象啊,來個例子你就明白了

      typealias?Token?=?String

      inline?class?TokenWrapper(val?value:?String)

      fun?main(args:?Array)?{

      val?token:?Token?=?"r3huae03zdhreol38fdjhkdfd8df"http://可以看出這里Token名稱完全是當做String類型來用了,相當于給String取了一個有意義的名字

      val?tokenWrapper?=?TokenWrapper("r3huae03zdhreol38fdjhkdfd8df")//而inline?class則是把String類型的值包裹起來,相當于String的一個包裝器類。

      println("token?is?$token")

      println("token?value?is?${tokenWrapper.value}")//這里tokenWrapper并不能像token一樣當做String來使用,而是需要打開包裝器取里面value值

      }

      通過反編譯分析,就能清楚明白為什么inline class不會存在創建對象性能開銷。實際上inline class在運行時表現和直接使用基礎類型的效果是一樣的。

      就拿上述例子反編譯分析一下:

      TokenWrapper類

      public?final?class?TokenWrapper?{

      @NotNull

      private?final?String?value;

      @NotNull

      public?final?String?getValue()?{//這個方法就不用說了吧,val自動生成的get方法

      return?this.value;

      }

      //?$FF:?synthetic?method

      private?TokenWrapper(@NotNull?String?value)?{//構造器私有化

      Intrinsics.checkParameterIsNotNull(value,?"value");

      super();

      this.value?=?value;

      }

      @NotNull

      public?static?String?constructor_impl/*?$FF?was:?constructor-impl*/(@NotNull?String?value)?{

      Intrinsics.checkParameterIsNotNull(value,?"value");

      return?value;

      }

      //?$FF:?synthetic?method

      @NotNull

      public?static?final?TokenWrapper?box_impl/*?$FF?was:?box-impl*/(@NotNull?String?v)?{//box-impl裝箱操作

      Intrinsics.checkParameterIsNotNull(v,?"v");

      return?new?TokenWrapper(v);

      }

      @NotNull

      public?static?String?toString_impl/*?$FF?was:?toString-impl*/(String?var0)?{//toString方法實現

      return?"TokenWrapper(value="?+?var0?+?")";

      }

      public?static?int?hashCode_impl/*?$FF?was:?hashCode-impl*/(String?var0)?{//hashCode方法實現

      return?var0?!=?null???var0.hashCode()?:?0;

      }

      public?static?boolean?equals_impl/*?$FF?was:?equals-impl*/(String?var0,?@Nullable?Object?var1)?{//equals方法實現

      if?(var1?instanceof?TokenWrapper)?{

      String?var2?=?((TokenWrapper)var1).unbox-impl();

      if?(Intrinsics.areEqual(var0,?var2))?{

      return?true;

      }

      }

      return?false;

      }

      public?static?final?boolean?equals_impl0/*?$FF?was:?equals-impl0*/(@NotNull?String?p1,?@NotNull?String?p2)?{

      Intrinsics.checkParameterIsNotNull(p1,?"p1");

      Intrinsics.checkParameterIsNotNull(p2,?"p2");

      throw?null;

      }

      //?$FF:?synthetic?method

      @NotNull

      public?final?String?unbox_impl/*?$FF?was:?unbox-impl*/()?{//拆箱操作

      return?this.value;

      }

      public?String?toString()?{

      return?toString-impl(this.value);//委托給對應靜態方法實現

      }

      public?int?hashCode()?{

      return?hashCode-impl(this.value);

      }

      public?boolean?equals(Object?var1)?{

      return?equals-impl(this.value,?var1);

      }

      }

      可以看到TokenWrapper類中反編譯后的源碼重寫了Any中toString、equal、hashCode三個方法。然后這三個方法又委托到給外部定義對應的靜態方法來實現。unbox_impl和box_impl兩個函數實際上就是拆箱和裝箱的操作

      main函數

      public?static?final?void?main(@NotNull?String[]?args)?{

      Intrinsics.checkParameterIsNotNull(args,?"args");

      String?token?=?"r3huae03zdhreol38fdjhkdfd8df";//可以看到typealias定義的Token名字已經消失的無影無蹤,只剩下String基礎類型。

      String?tokenWrapper?=?TokenWrapper.constructor-impl("r3huae03zdhreol38fdjhkdfd8df");//TokenWrapper類痕跡依然存在

      String?var3?=?"token?is?"?+?token;

      System.out.println(var3);

      var3?=?"token?value?is?"?+?tokenWrapper;

      System.out.println(var3);

      }

      分析如下: 可以先從main函數入手,重點看這行:

      String?tokenWrapper?=?TokenWrapper.constructor-impl("r3huae03zdhreol38fdjhkdfd8df");

      然后再跳到TokenWrapper中constructor-impl方法

      @NotNull

      public?static?String?constructor_impl/*?$FF?was:?constructor-impl*/(@NotNull?String?value)?{

      Intrinsics.checkParameterIsNotNull(value,?"value");

      return?value;//這里僅僅是接收一個value值,做了一個參數檢查,最后就直接把這個value又返回出去了。

      }

      所以main函數中的val tokenWrapper = TokenWrapper("r3huae03zdhreol38fdjhkdfd8df")在運行時相當于val tokenWrapper: String = "r3huae03zdhreol38fdjhkdfd8df". 所以性能問題就不用擔心了。

      1、內聯類必須含有主構造器且構造器內參數個數有且僅有一個,形參只能是只讀的(val修飾)。

      2、內聯類不能含有init block

      3、內聯類不能含有inner class

      二、when表達式的使用優化

      Kotlin1.3新特性對when表達式做一個寫法上的優化,為什么這么說呢?僅僅就是寫法上的優化,實際上什么都沒做,一起來研究下。不知道大家在使用when表達式有沒有這樣感受(反正我是有過這樣的感受): 在when表達式作用域內,老天啊請賜我一個像lambda表達式中的一樣it實例對象指代吧。---來自眾多Kotlin開發者心聲。一起看下這個例子:

      @JvmStatic

      private?fun?fillIntentArguments(intent:?Intent,?params:?Array>)?{

      params.forEach?{

      val?value?=?it.second//由于沒有像lamba那樣的it指代只能在when表達式最外層定義一個局部變量value,以便于在when表達式體內使用value.

      when?(value)?{

      null?->?intent.putExtra(it.first,?null?as?Serializable?)

      is?Int?->?intent.putExtra(it.first,?value)//可以看到這里,如果value能像lambda表達式中it指代該多好,可以沒有

      is?Long?->?intent.putExtra(it.first,?value)

      is?CharSequence?->?intent.putExtra(it.first,?value)

      is?String?->?intent.putExtra(it.first,?value)

      is?Float?->?intent.putExtra(it.first,?value)

      is?Double?->?intent.putExtra(it.first,?value)

      ...

      }

      return@forEach

      }

      }

      可以看到上面的1.3版本之前源碼案例實現,本就一個when表達式的實現由于在表達式內部需要使用傳入值,但是呢表達式作用域內又不能像lambda表達式內部那樣快樂使用it,所以被活生生拆成兩行代碼實現,是不是很郁悶。關于這個問題,官方已經注意到了,可以看到Kotlin團隊的大佬們對開發者的問題處理還是蠻積極的,馬上就優化這個問題。

      官方到底是怎么優化的呢? 那么有的人就說了是不是像lambda表達式一樣賜予我們一個it指代呢。官方的回答是: NO. 一起再來看1.3版本的實現:

      private?fun?fillIntentArguments(intent:?Intent,?params:?Array>)?{

      params.forEach?{

      when?(val?value?=?it.second)?{//看到沒有,官方說你不是想要一個when表達式實現嗎,那行把value縮進來了.?這樣在when表達式內部快樂使用value了

      null?->?intent.putExtra(it.first,?null?as?Serializable?)

      is?Int?->?intent.putExtra(it.first,?value)

      is?Long?->?intent.putExtra(it.first,?value)

      is?CharSequence?->?intent.putExtra(it.first,?value)

      is?String?->?intent.putExtra(it.first,?value)

      is?Float?->?intent.putExtra(it.first,?value)

      is?Double?->?intent.putExtra(it.first,?value)

      ...

      }

      return@forEach

      }

      }

      kotlin 1.3版本之前when表達式實現

      fun?main(args:?Array)?{

      val?value?=?getValue()

      when?(value)?{

      is?Int?->?"This?is?Int?Type,?value?is?$value".apply(::println)

      is?String?->?"This?is?String?Type,?value?is?$value".apply(::println)

      is?Double?->?"This?is?Double?Type,?value?is?$value".apply(::println)

      is?Float?->?"This?is?Float?Type,?value?is?$value".apply(::println)

      else?->?"unknown?type".apply(::println)

      }

      }

      fun?getValue():?Any?{

      return?100F

      }

      kotlin 1.3版本之前when表達式使用反編譯代碼

      public?static?final?void?main(@NotNull?String[]?args)?{

      Intrinsics.checkParameterIsNotNull(args,?"args");

      Object?value?=?getValue();

      String?var3;

      if?(value?instanceof?Integer)?{

      var3?=?"This?is?Int?Type,?value?is?"?+?value;

      System.out.println(var3);

      }?else?if?(value?instanceof?String)?{

      var3?=?"This?is?String?Type,?value?is?"?+?value;

      System.out.println(var3);

      }?else?if?(value?instanceof?Double)?{

      var3?=?"This?is?Double?Type,?value?is?"?+?value;

      System.out.println(var3);

      }?else?if?(value?instanceof?Float)?{

      var3?=?"This?is?Float?Type,?value?is?"?+?value;

      System.out.println(var3);

      }?else?{

      var3?=?"unknown?type";

      System.out.println(var3);

      Jetbrains開發者日見聞(三)之Kotlin1.3新特性(inline class篇)

      }

      }

      @NotNull

      public?static?final?Object?getValue()?{

      return?100.0F;

      }

      kotlin 1.3版本when表達式實現

      fun?main(args:?Array)?{

      when?(val?value?=?getValue())?{//when表達式條件直接是一個表達式,并用value保存了返回值

      is?Int?->?"This?is?Int?Type,?value?is?$value".apply(::println)

      is?String?->?"This?is?String?Type,?value?is?$value".apply(::println)

      is?Double?->?"This?is?Double?Type,?value?is?$value".apply(::println)

      is?Float?->?"This?is?Float?Type,?value?is?$value".apply(::println)

      else?->?"unknown?type".apply(::println)

      }

      }

      fun?getValue():?Any?{

      return?100F

      }

      kotlin 1.3版本when表達式使用反編譯代碼

      public?static?final?void?main(@NotNull?String[]?args)?{

      Intrinsics.checkParameterIsNotNull(args,?"args");

      Object?value?=?getValue();

      String?var2;

      if?(value?instanceof?Integer)?{

      var2?=?"This?is?Int?Type,?value?is?"?+?value;

      System.out.println(var2);

      }?else?if?(value?instanceof?String)?{

      var2?=?"This?is?String?Type,?value?is?"?+?value;

      System.out.println(var2);

      }?else?if?(value?instanceof?Double)?{

      var2?=?"This?is?Double?Type,?value?is?"?+?value;

      System.out.println(var2);

      }?else?if?(value?instanceof?Float)?{

      var2?=?"This?is?Float?Type,?value?is?"?+?value;

      System.out.println(var2);

      }?else?{

      var2?=?"unknown?type";

      System.out.println(var2);

      }

      }

      @NotNull

      public?static?final?Object?getValue()?{

      return?100.0F;

      }

      通過對比兩者實現方式反編譯的代碼你會發現沒有任何變化,所以這就是我說為什么實際上沒做什么操作。

      三、無參的main函數

      還記得開發者日大會上官方布道師Hali在講Kotlin 1.3新特性的時候,第一個例子就是講無參數main函數,在他認為這是一件很興奮的事。下面給出官方一張動圖一起興奮一下:

      不知道大家在開發中有沒有被其他動態語言開發的人吐槽過。比如最簡單的在程序中打印一行內容的時候,靜態語言就比較繁瑣用Java舉例先得定義一個類,然后再定義main函數,函數中還得傳入數組參數。人家python一行print代碼就解決了。其實Kotlin之前版本相對Java還是比較簡單至少不需要定義類了,但是Kotlin 1.3就直接把main函數中的參數干掉了(注意: 這里指的是帶參數和不帶參數共存,并不是完全把帶參main函數給替換掉了)。

      可以大家有沒有思考過無參main函數是怎么實現的呢? 不妨我們一起來探索一波,來了你就懂了,很簡單。

      來個Hello Kotlin的例子哈。

      fun?main(){

      println("Hello?Kotlin")

      }

      將上述代碼反編譯成Java代碼如下

      public?final?class?NewMainKt?{

      public?static?final?void?main()?{//外部定義無參的main函數

      String?var0?=?"Hello?Kotlin";

      System.out.println(var0);

      }

      //?$FF:?synthetic?method

      public?static?void?main(String[]?var0)?{//自動生成一個帶參數的main函數

      main();//然后再去調用一個無參的main函數

      }

      }

      看完反編譯后的Java代碼是不是一眼就清楚,所謂的無參main函數,實際上就是個障眼法。默認生成一個帶參數的main函數繼續作為執行的入口,只不過在這帶參數的main函數中再去調用外部無參main函數。

      注意: 使用無參main函數有好處也有不妥的地方,好處顯而易見的是使用非常簡潔。但是也就間接喪失了main函數執行入口配置參數功能。所以官方并沒有把帶參數main函數去掉,而是共存。兩種main函數都是有各自使用場景的。

      四、接口的伴生對象支持@JvmStatic,@JvmField

      我們自然而然知道在類的伴生對象是完全支持@JvmStatic,@JvmField注解。首先呢,關于@JvmStatic,@JvmField注解我想有必要說明下它們的作用。

      @JvmStatic,@JvmField的作用(實際上以前文章中有提到過)

      他們作用主要是為了在Kotlin伴生對象中定義的一個函數或屬性,能夠在Java中像調用靜態函數和靜態屬性那樣類名.函數名/屬性名方式調用,讓Java開發者完全無法感知這是一個來自Kotlin伴生對象中的函數或屬性。如果不加注解那么在Java中調用方式就是類名.Companion.函數名/屬性名。你讓一個Java開發者知道Companion存在,只會讓他一臉懵逼。

      Kotlin 1.3版本接口(interface)中伴生對象支持@JvmStatic,@JvmField

      這就意味著1.3接口中伴生對象中函數和屬性可以向類中一樣快樂地使用@JvmStatic,@JvmField注解了。

      一起來看個使用例子:

      //在Kotlin接口中定義

      interface?Foo?{

      companion?object?{

      @JvmField

      val?answer:?Int?=?42

      @JvmStatic

      fun?sayHello()?{

      println("Hello,?world!")

      }

      }

      }

      //在Java代碼中調用

      class?TestFoo?{

      public?static?void?main(String[]?args){

      System.out.println("Foo?test:?"?+?Foo.answer?+?"?say:?"?+?Foo.sayHello());

      }

      }

      五、支持可變參數的FunctionN接口

      不知道大家是否還記得我之前幾篇文章深入研究過Lambda表達式整個運行原理,其中就詳細講了關于Function系列的接口。因為我們知道Lambda表達式最后會編譯成一個class類,這個類會去繼承Kotlin中Lambda的抽象類(在kotlin.jvm.internal包中)并且實現一個Function0…FunctionN(在kotlin.jvm.functions包中)的接口(這個N是根據lambda表達式傳入參數的個數決定的,目前接口N的取值為 0 <= N <= 22,也就是lambda表達式中函數傳入的參數最多也只能是22個),這個Lambda抽象類是實現了FunctionBase接口,該接口中有兩個方法一個是getArity()獲取lambda參數的元數,toString()實際上就是打印出Lambda表達式類型字符串,獲取Lambda表達式類型字符串是通過Java中Reflection類反射來實現的。FunctionBase接口繼承了Function,Serializable接口。(具體詳細內容請參考我的這篇文章: 淺談Kotlin語法篇之lambda編譯成字節碼過程完全解析(七))

      由上面分析得到N取值范圍是0 <= N <= 22,要是此時有個lambda表達式函數參數個數是23個也就是大于22個時候該怎么辦?不就玩不轉了嗎? 雖然大于22個參數場景很少很少,但是這始終算是一個缺陷。所以這次Kotlin 1.3直接就徹底抹平這個缺陷,增加了FunctionN接口支持傳入的是可變長參數列表,也就是支持任意個數參數,這樣擴展性就更強了。

      //官方源碼定義

      interface?FunctionN?:?Function,?FunctionBase?{

      /**

      *?Invokes?the?function?with?the?specified?arguments.

      *

      *?Must?**throw?exception**?if?the?length?of?passed?[args]?is?not?equal?to?the?parameter?count?returned?by?[arity].

      *

      *?@param?args?arguments?to?the?function

      */

      operator?fun?invoke(vararg?args:?Any?):?R//可以看到這里接收是一個vararg?可變長參數,支持任意個數的lambda表達式參數,再也不用擔心超過22個參數該怎么辦了。

      /**

      *?Returns?the?number?of?arguments?that?must?be?passed?to?this?function.

      */

      override?val?arity:?Int

      }

      //使用例子偽代碼

      fun?trueEnterpriseComesToKotlin(block:?(Any,?Any,?...?/*?42?more?*/,?Any)?->?Any)?{

      block(Any(),?Any(),?...,?Any())

      }

      六、注解類的嵌套聲明

      在Kotlin 1.3中,注解類可以嵌套注解類、接口以及伴生對象.關于Kotlin中的注解和反射還沒有詳細深入研究過,這個暫且放一放,等到研究注解時候,會再次探討有關注解類嵌套的問題。

      annotation?class?Foo?{

      enum?class?Direction?{?UP,?DOWN,?LEFT,?RIGHT?}

      annotation?class?Bar

      companion?object?{

      fun?foo():?Int?=?42

      val?bar:?Int?=?42

      }

      }

      七、結語

      到這里Kotlin1.3新特性相關的內容就結束。下面將會繼續深入研究下Kotlin 1.3中的inline class(主要是以翻譯國外優秀文章為主)。然后就是去深入研究大家一直期待的協程和ktor框架,并把最終研究成果以文章的形式共享給大家。歡迎關注,會一直持續更新下去~~~

      Kotlin系列文章,歡迎查看:

      原創系列:

      jetbrains開發者日見聞(二)之Kotlin1.3的新特性(Contract契約與協程篇)

      JetBrains開發者日見聞(一)之Kotlin/Native 嘗鮮篇

      教你如何攻克Kotlin中泛型型變的難點(實踐篇)

      教你如何攻克Kotlin中泛型型變的難點(下篇)

      教你如何攻克Kotlin中泛型型變的難點(上篇)

      Kotlin的獨門秘籍Reified實化類型參數(下篇)

      有關Kotlin屬性代理你需要知道的一切

      淺談Kotlin中的Sequences源碼解析

      淺談Kotlin中集合和函數式API完全解析-上篇

      淺談Kotlin語法篇之lambda編譯成字節碼過程完全解析

      淺談Kotlin語法篇之Lambda表達式完全解析

      淺談Kotlin語法篇之擴展函數

      淺談Kotlin語法篇之頂層函數、中綴調用、解構聲明

      淺談Kotlin語法篇之如何讓函數更好地調用

      淺談Kotlin語法篇之變量和常量

      淺談Kotlin語法篇之基礎語法

      翻譯系列:

      [譯]Kotlin的獨門秘籍Reified實化類型參數(上篇)

      [譯]Kotlin泛型中何時該用類型形參約束?

      [譯] 一個簡單方式教你記住Kotlin的形參和實參

      [譯]Kotlin中是應該定義函數還是定義屬性?

      [譯]如何在你的Kotlin代碼中移除所有的!!(非空斷言)

      [譯]掌握Kotlin中的標準庫函數: run、with、let、also和apply

      [譯]有關Kotlin類型別名(typealias)你需要知道的一切

      [譯]Kotlin中是應該使用序列(Sequences)還是集合(Lists)?

      [譯]Kotlin中的龜(List)兔(Sequence)賽跑

      [譯]Effective Kotlin系列之考慮使用靜態工廠方法替代構造器

      [譯]Effective Kotlin系列之遇到多個構造器參數要考慮使用構建器

      實戰系列:

      用Kotlin擼一個圖片壓縮插件ImageSlimming-導學篇(一)

      用Kotlin擼一個圖片壓縮插件-插件基礎篇(二)

      用Kotlin擼一個圖片壓縮插件-實戰篇(三)

      淺談Kotlin實戰篇之自定義View圖片圓角簡單應用

      歡迎關注Kotlin開發者聯盟,這里有最新Kotlin技術文章,每周會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

      Java Kotlin

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:在線文檔怎么才能復制粘貼(在線文檔如何復制粘貼)
      下一篇:ppt中嵌入顯示此文檔包含的嵌入內容可能對您的計算機有害怎么弄(ppt中下列有關鏈接與嵌入的說法中錯誤的是)
      相關文章
      亚洲精品无码专区在线| 亚洲国产精品尤物YW在线观看| 国产婷婷综合丁香亚洲欧洲| 亚洲资源在线观看| 国产亚洲精AA在线观看SEE| 亚洲男人天堂2020| 亚洲成av人片一区二区三区| 亚洲.国产.欧美一区二区三区| 亚洲欧美日韩中文字幕一区二区三区| 国产成人精品日本亚洲18图| 亚洲av永久无码精品天堂久久| 亚洲videos| wwwxxx亚洲| 亚洲日韩AV一区二区三区四区| 亚洲日韩AV一区二区三区四区| 亚洲gay片在线gv网站| 亚洲AV综合色区无码一二三区| 人人狠狠综合久久亚洲| 亚洲AV性色在线观看| 亚洲成aⅴ人片久青草影院按摩| 亚洲AV女人18毛片水真多| 国产亚洲美女精品久久久久| 亚洲AV本道一区二区三区四区| 亚洲中文字幕无码中文字在线| 亚洲日韩涩涩成人午夜私人影院| 亚洲成AⅤ人影院在线观看| 亚洲国产精品人人做人人爱| 亚洲午夜无码AV毛片久久| 亚洲一区爱区精品无码| 久久亚洲高清观看| 亚洲综合在线观看视频| 亚洲国产精品综合久久2007| 国产成人精品日本亚洲直接| 亚洲AV无码之国产精品| 亚洲精品99久久久久中文字幕| 综合亚洲伊人午夜网| 久久青青草原亚洲AV无码麻豆| 亚洲综合激情另类小说区| 亚洲一区二区三区在线| 亚洲精品动漫免费二区| 亚洲国产一区明星换脸|