Jetbrains開發(fā)者日見聞(三)之Kotlin1.3新特性有哪些?

簡述:
上接上篇文章,我們深入分析了Kotlin1.3版本中的Contract契約的內(nèi)容开缎,那么這篇文章將會(huì)繼續(xù)把Kotlin1.3新特性研究完畢撩幽。這篇文章還有個(gè)非常重要的點(diǎn)就是inline class 內(nèi)聯(lián)類昌跌。關(guān)于內(nèi)聯(lián)類的知識(shí)除了這篇文章會(huì)有介紹剩失,后面馬上會(huì)翻譯幾篇有關(guān)Kotlin中的內(nèi)聯(lián)類相關(guān)內(nèi)容。只有一個(gè)目的徹底搞定Kotlin中的內(nèi)聯(lián)類。那我們一起來看下本次提綱:

image

一担锤、inline class內(nèi)聯(lián)類(Experimental)

1、復(fù)習(xí)inline內(nèi)聯(lián)函數(shù)的作用

關(guān)于inline內(nèi)聯(lián)相信大家都不陌生吧乍钻,在實(shí)際開發(fā)中我們經(jīng)常會(huì)使用Kotlin中的inline內(nèi)聯(lián)函數(shù)。那大家還記得inline內(nèi)聯(lián)作用嗎? 這里再次復(fù)習(xí)下inline內(nèi)聯(lián)的作用:

inline內(nèi)聯(lián)函數(shù)主要有兩大作用:

  • 用于lambda表達(dá)式調(diào)用铭腕,降低Function系列對(duì)象實(shí)例創(chuàng)建的內(nèi)存開銷银择,從而提高性能。聲明成內(nèi)聯(lián)函數(shù)的話累舷,而是在調(diào)用的時(shí)把調(diào)用的方法給替換掉浩考,可以降低很大的性能開銷。

  • 另一個(gè)內(nèi)聯(lián)函數(shù)作用就是它能是泛型函數(shù)類型實(shí)參進(jìn)行實(shí)化被盈,在運(yùn)行時(shí)能拿到類型實(shí)參的信息析孽。

2、為何需要內(nèi)聯(lián)類

通過復(fù)習(xí)了inline函數(shù)的兩大作用只怎,實(shí)際上內(nèi)聯(lián)類存在意義和inline函數(shù)第一個(gè)作用有點(diǎn)像袜瞬。有時(shí)候業(yè)務(wù)場景需要針對(duì)某種類型創(chuàng)建包裝器類。 但是身堡,使用包裝器類避免不了去實(shí)例化這個(gè)包裝器類邓尤,但是這樣會(huì)帶來額外的創(chuàng)建對(duì)象的堆分配,它會(huì)引入運(yùn)行時(shí)開銷贴谎。 此外汞扎,如果包裝類型是基礎(chǔ)類型的,性能損失是很糟糕的擅这,因?yàn)榛A(chǔ)類型通常在運(yùn)行時(shí)大大優(yōu)化澈魄,而它們的包裝器沒有得到任何特殊處理。

那到底是什么場景會(huì)需要內(nèi)聯(lián)類呢? 實(shí)際上仲翎,在開發(fā)中有時(shí)候就基本數(shù)據(jù)類型外加變量名是無法完全表達(dá)某個(gè)字段的含義痹扇,甚至還有可能造成歧義,這種場景就特別適合內(nèi)聯(lián)類包裝器谭确。是不是很抽象,那么一起來看個(gè)例子帘营。

  • 案例一: Iterable擴(kuò)展函數(shù)joinToString源碼分析
public fun <T> Iterable<T>.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()
}

我們仔細(xì)分析下joinToString函數(shù),它有很多個(gè)函數(shù)參數(shù)逐哈,其中讓人感到歧義就是前面三個(gè): separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "".有的人就會(huì)說不會(huì)有歧義啊芬迄,定義得很清楚很明白了,separator,prefix,postfix形參名明顯都有自己的含義啊昂秃。僅僅從joinToString這個(gè)函數(shù)的角度來看確實(shí)比較清晰禀梳《耪可是你有沒有想過外層調(diào)用者困惑呢。對(duì)于外部調(diào)用者前面三個(gè)參數(shù)都是CharSequence類型算途,對(duì)于他們而言除非去看函數(shù)聲明然后才知道每個(gè)參數(shù)代表什么意思塞耕,外面調(diào)用者很容易把三個(gè)參數(shù)調(diào)用順序弄混了。就像下面這樣嘴瓤。

fun main(args: Array<String>) {
    val numberList = listOf(1, 3, 5, 7, 9, 11, 13)
    println(numberList.joinToStr("<",",",">"))
    //這段代碼在調(diào)用者看來就僅僅是傳入三個(gè)字符串扫外,給人看起來很迷惑,根本就不知道每個(gè)字符串實(shí)參到底代表是什么意思廓脆。
    //這里代碼是很脆弱的筛谚,在不看函數(shù)聲明時(shí),字符串要么很容易被打亂順序停忿,要么沒人敢改這里的代碼了驾讲。
}

fun <T> Iterable<T>.joinToStr(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = ""): String{
    return this.joinToString(separator, prefix, postfix)
}

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

第一種: IDE高亮提示

image

第二種: Kotlin中命名參數(shù)

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

fun main(args: Array<String>) {
    val numberList = listOf(1, 3, 5, 7, 9, 11, 13)
    println(numberList.joinToStr(prefix = "<", separator = ",", postfix = ">"))
}

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

fun main(args: Array<String>) {
    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 <T> Iterable<T>.joinToStr(
    separator: Speparator,
    prefix: Prefix,
    postfix: Postfix
): String {
    return this.joinToString(separator.separator, prefix.prefix, postfix.postfix)
}

看到這里是不是很多人覺得這樣實(shí)現(xiàn)有問題谓晌,雖然它能很好解決我們上述類型不明確的問題。但是卻引入一個(gè)更大問題便监,需要額外創(chuàng)建Speparator扎谎、Prefix、Postfix實(shí)例對(duì)象烧董,帶來很多的內(nèi)存開銷毁靶。從投入產(chǎn)出比來看,估計(jì)沒人這么玩吧逊移。這是因?yàn)閕nline class沒出來之前预吆,但是如果inline class能把性能開銷降低到和直接使用String一樣的性能開銷,你還認(rèn)為它是很差的方案嗎? 請(qǐng)接著往下看

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

針對(duì)上述問題胳泉,Kotlin提出inline class解決方案拐叉,就是從源頭上解決問題。一起來看看:

fun main(args: Array<String>) {
    val numberList = listOf(1, 3, 5, 7, 9, 11, 13)
    println(numberList.joinToStr(Speparator(","), Prefix("<"), Postfix(">")))
}

//相比上一個(gè)方案扇商,僅僅是多加了inline關(guān)鍵字
inline class Speparator(val separator: CharSequence)
inline class Prefix(val prefix: CharSequence)
inline class Postfix(val postfix: CharSequence)

fun <T> Iterable<T>.joinToStr(
    separator: Speparator,
    prefix: Prefix,
    postfix: Postfix
): String {
    return this.joinToString(separator.separator, prefix.prefix, postfix.postfix)
}

通過使用inline class來改造這個(gè)案例凤瘦,會(huì)發(fā)現(xiàn)剛剛上述那個(gè)問題就被徹底解決了,外部調(diào)用者不會(huì)再一臉懵逼了案铺,一看就很明確蔬芥,是該傳入Speparator、Prefix、Postfix對(duì)象笔诵。性能開銷問題的完全不用擔(dān)心了返吻,它和直接使用String的性能幾乎是一樣的,這樣一來是不是覺得這種解決方案還不錯(cuò)呢乎婿。至于它是如何做到的测僵,請(qǐng)接著往下看。這就是為什么需要inline class場景了谢翎。

3捍靠、內(nèi)聯(lián)類的基本介紹

  • 基本定義

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

  • 基本結(jié)構(gòu)

基本結(jié)構(gòu)很簡單就是在普通class前面加上inline關(guān)鍵字

4森逮、如何嘗鮮inline class

因?yàn)閗otlin中的inline class還是處于Experimental中剂公,所以你要使用它需要做一些額外的配置。首先你的Kotlin Plugin升級(jí)到1.3版以上吊宋,然后配置gradle,這里給出IntelliJ IDEA和AndroidStudio嘗鮮gradle配置:

  • IntelliJ IDEA中g(shù)radle配置
compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
    kotlinOptions {
        freeCompilerArgs = ["-XXLanguage:+InlineClasses"]
    }
}
  • AndroidStudio中g(shù)radle配置
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        jvmTarget = '1.8'
        freeCompilerArgs = ['-XXLanguage:+InlineClasses']
    }
}
  • 使用maven的配置
 <configuration>
                <args>
                    <arg>-XXLanguage:+InlineClasses</arg>
                </args>
 </configuration>

5、內(nèi)聯(lián)類和typealias的區(qū)別

估計(jì)很多人都沒使用過typealias吧颜武,如果還不了解typealias的話請(qǐng)參考我之前的這篇文章:[譯]有關(guān)Kotlin類型別名(typealias)你需要知道的一切. 其實(shí)上述那個(gè)問題還可以用typealias來改寫璃搜,但是你會(huì)發(fā)現(xiàn)是有點(diǎn)缺陷的,并沒有達(dá)到想要效果鳞上≌馕牵可以給大家看下:

typealias Speparator = CharSequence
typealias Prefix = CharSequence
typealias Postfix = CharSequence

fun main(args: Array<String>) {
    val numberList = listOf(1, 3, 5, 7, 9, 11, 13)
    println(numberList.joinToStr(",", "<", ">"))
}

fun <T> Iterable<T>.joinToStr(
    separator: Speparator,
    prefix: Prefix,
    postfix: Postfix
): String {
    return this.joinToString(separator, prefix, postfix)
}

關(guān)于inline class和typealias有很大相同點(diǎn)不同點(diǎn),相同點(diǎn)在于: 他們兩者看起來貌似都引入一種新類型篙议,并且兩者都將在運(yùn)行時(shí)表現(xiàn)為基礎(chǔ)類型唾糯。不同點(diǎn)在于: typealias僅僅是給基礎(chǔ)類型取了一個(gè)別名而已,而inline class是基礎(chǔ)類型一個(gè)包裝器類鬼贱。換句話說inline class才是真正引入了一個(gè)新的類型移怯,而typealias則沒有。

是不是還是有點(diǎn)抽象啊这难,來個(gè)例子你就明白了

typealias Token = String
inline class TokenWrapper(val value: String)

fun main(args: Array<String>) {
    val token: Token = "r3huae03zdhreol38fdjhkdfd8df"http://可以看出這里Token名稱完全是當(dāng)做String類型來用了舟误,相當(dāng)于給String取了一個(gè)有意義的名字
    val tokenWrapper = TokenWrapper("r3huae03zdhreol38fdjhkdfd8df")//而inline class則是把String類型的值包裹起來,相當(dāng)于String的一個(gè)包裝器類姻乓。

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

6、內(nèi)聯(lián)類的反編譯源碼分析

通過反編譯分析蹋岩,就能清楚明白為什么inline class不會(huì)存在創(chuàng)建對(duì)象性能開銷赖草。實(shí)際上inline class在運(yùn)行時(shí)表現(xiàn)和直接使用基礎(chǔ)類型的效果是一樣的。

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

TokenWrapper類

public final class TokenWrapper {
   @NotNull
   private final String value;

   @NotNull
   public final String getValue() {//這個(gè)方法就不用說了吧剪个,val自動(dòng)生成的get方法
      return this.value;
   }

   // $FF: synthetic method
   private TokenWrapper(@NotNull String value) {//構(gòu)造器私有化
      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方法實(shí)現(xiàn)
      return "TokenWrapper(value=" + var0 + ")";
   }

   public static int hashCode_impl/* $FF was: hashCode-impl*/(String var0) {//hashCode方法實(shí)現(xiàn)
      return var0 != null ? var0.hashCode() : 0;
   }

   public static boolean equals_impl/* $FF was: equals-impl*/(String var0, @Nullable Object var1) {//equals方法實(shí)現(xiàn)
      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);//委托給對(duì)應(yīng)靜態(tài)方法實(shí)現(xiàn)
   }

   public int hashCode() {
      return hashCode-impl(this.value);
   }

   public boolean equals(Object var1) {
      return equals-impl(this.value, var1);
   }
}

可以看到TokenWrapper類中反編譯后的源碼重寫了Any中toString秧骑、equal、hashCode三個(gè)方法。然后這三個(gè)方法又委托到給外部定義對(duì)應(yīng)的靜態(tài)方法來實(shí)現(xiàn)腿堤。unbox_impl和box_impl兩個(gè)函數(shù)實(shí)際上就是拆箱和裝箱的操作

main函數(shù)

 public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      String token = "r3huae03zdhreol38fdjhkdfd8df";//可以看到typealias定義的Token名字已經(jīng)消失的無影無蹤阀坏,只剩下String基礎(chǔ)類型。
      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函數(shù)入手笆檀,重點(diǎn)看這行:

 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;//這里僅僅是接收一個(gè)value值忌堂,做了一個(gè)參數(shù)檢查,最后就直接把這個(gè)value又返回出去了酗洒。
   }

所以main函數(shù)中的val tokenWrapper = TokenWrapper("r3huae03zdhreol38fdjhkdfd8df")在運(yùn)行時(shí)相當(dāng)于val tokenWrapper: String = "r3huae03zdhreol38fdjhkdfd8df". 所以性能問題就不用擔(dān)心了士修。

7、內(nèi)聯(lián)類使用限制

  • 1樱衷、內(nèi)聯(lián)類必須含有主構(gòu)造器且構(gòu)造器內(nèi)參數(shù)個(gè)數(shù)有且僅有一個(gè)棋嘲,形參只能是只讀的(val修飾)。
  • 2矩桂、內(nèi)聯(lián)類不能含有init block
  • 3沸移、內(nèi)聯(lián)類不能含有inner class

二、when表達(dá)式的使用優(yōu)化

Kotlin1.3新特性對(duì)when表達(dá)式做一個(gè)寫法上的優(yōu)化侄榴,為什么這么說呢?僅僅就是寫法上的優(yōu)化雹锣,實(shí)際上什么都沒做,一起來研究下癞蚕。不知道大家在使用when表達(dá)式有沒有這樣感受(反正我是有過這樣的感受): 在when表達(dá)式作用域內(nèi)蕊爵,老天啊請(qǐng)賜我一個(gè)像lambda表達(dá)式中的一樣it實(shí)例對(duì)象指代吧。---來自眾多Kotlin開發(fā)者心聲桦山。一起看下這個(gè)例子:

1攒射、Anko庫源碼中fillIntentArguments函數(shù)部分代碼分析

  @JvmStatic
    private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
        params.forEach {
    val value = it.second//由于沒有像lamba那樣的it指代只能在when表達(dá)式最外層定義一個(gè)局部變量value,以便于在when表達(dá)式體內(nèi)使用value.
            when (value) {
                null -> intent.putExtra(it.first, null as Serializable?)
                is Int -> intent.putExtra(it.first, value)//可以看到這里,如果value能像lambda表達(dá)式中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版本之前源碼案例實(shí)現(xiàn)会放,本就一個(gè)when表達(dá)式的實(shí)現(xiàn)由于在表達(dá)式內(nèi)部需要使用傳入值,但是呢表達(dá)式作用域內(nèi)又不能像lambda表達(dá)式內(nèi)部那樣快樂使用it,所以被活生生拆成兩行代碼實(shí)現(xiàn)钉凌,是不是很郁悶鸦概。關(guān)于這個(gè)問題,官方已經(jīng)注意到了甩骏,可以看到Kotlin團(tuán)隊(duì)的大佬們對(duì)開發(fā)者的問題處理還是蠻積極的窗市,馬上就優(yōu)化這個(gè)問題。

2饮笛、1.3版本when表達(dá)式優(yōu)化版本

官方到底是怎么優(yōu)化的呢? 那么有的人就說了是不是像lambda表達(dá)式一樣賜予我們一個(gè)it指代呢咨察。官方的回答是: NO. 一起再來看1.3版本的實(shí)現(xiàn):

private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
    params.forEach {
        when (val value = it.second) {//看到?jīng)]有,官方說你不是想要一個(gè)when表達(dá)式實(shí)現(xiàn)嗎福青,那行把value縮進(jìn)來了. 這樣在when表達(dá)式內(nèi)部快樂使用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
    }
}

3摄狱、優(yōu)化之后反編譯代碼對(duì)比

  • kotlin 1.3版本之前when表達(dá)式實(shí)現(xiàn)
fun main(args: Array<String>) {
    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表達(dá)式使用反編譯代碼
  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);
      }

   }

   @NotNull
   public static final Object getValue() {
      return 100.0F;
   }
  • kotlin 1.3版本when表達(dá)式實(shí)現(xiàn)
  fun main(args: Array<String>) {
    when (val value = getValue()) {//when表達(dá)式條件直接是一個(gè)表達(dá)式脓诡,并用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表達(dá)式使用反編譯代碼
     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;
   }

通過對(duì)比兩者實(shí)現(xiàn)方式反編譯的代碼你會(huì)發(fā)現(xiàn)沒有任何變化,所以這就是我說為什么實(shí)際上沒做什么操作媒役。

三祝谚、無參的main函數(shù)

還記得開發(fā)者日大會(huì)上官方布道師Hali在講Kotlin 1.3新特性的時(shí)候,第一個(gè)例子就是講無參數(shù)main函數(shù)酣衷,在他認(rèn)為這是一件很興奮的事交惯。下面給出官方一張動(dòng)圖一起興奮一下:

image

不知道大家在開發(fā)中有沒有被其他動(dòng)態(tài)語言開發(fā)的人吐槽過。比如最簡單的在程序中打印一行內(nèi)容的時(shí)候穿仪,靜態(tài)語言就比較繁瑣用Java舉例先得定義一個(gè)類席爽,然后再定義main函數(shù),函數(shù)中還得傳入數(shù)組參數(shù)啊片。人家python一行print代碼就解決了师痕。其實(shí)Kotlin之前版本相對(duì)Java還是比較簡單至少不需要定義類了棘钞,但是Kotlin 1.3就直接把main函數(shù)中的參數(shù)干掉了(注意: 這里指的是帶參數(shù)和不帶參數(shù)共存,并不是完全把帶參main函數(shù)給替換掉了)闸昨。

可以大家有沒有思考過無參main函數(shù)是怎么實(shí)現(xiàn)的呢? 不妨我們一起來探索一波商佑,來了你就懂了帽驯,很簡單哀托。
來個(gè)Hello Kotlin的例子哈移斩。

fun main(){
    println("Hello Kotlin")
}

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

public final class NewMainKt {
   public static final void main() {//外部定義無參的main函數(shù)
      String var0 = "Hello Kotlin";
      System.out.println(var0);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {//自動(dòng)生成一個(gè)帶參數(shù)的main函數(shù)
      main();//然后再去調(diào)用一個(gè)無參的main函數(shù)
   }
}

看完反編譯后的Java代碼是不是一眼就清楚,所謂的無參main函數(shù)咬腋,實(shí)際上就是個(gè)障眼法。默認(rèn)生成一個(gè)帶參數(shù)的main函數(shù)繼續(xù)作為執(zhí)行的入口睡互,只不過在這帶參數(shù)的main函數(shù)中再去調(diào)用外部無參main函數(shù)根竿。

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

四、接口的伴生對(duì)象支持@JvmStatic,@JvmField

我們自然而然知道在類的伴生對(duì)象是完全支持@JvmStatic,@JvmField注解逼侦。首先呢匿辩,關(guān)于@JvmStatic,@JvmField注解我想有必要說明下它們的作用。

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

他們作用主要是為了在Kotlin伴生對(duì)象中定義的一個(gè)函數(shù)或?qū)傩蚤欢軌蛟贘ava中像調(diào)用靜態(tài)函數(shù)和靜態(tài)屬性那樣類名.函數(shù)名/屬性名方式調(diào)用铲球,讓Java開發(fā)者完全無法感知這是一個(gè)來自Kotlin伴生對(duì)象中的函數(shù)或?qū)傩浴H绻患幼⒔饽敲丛贘ava中調(diào)用方式就是類名.Companion.函數(shù)名/屬性名晰赞。你讓一個(gè)Java開發(fā)者知道Companion存在稼病,只會(huì)讓他一臉懵逼选侨。

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

這就意味著1.3接口中伴生對(duì)象中函數(shù)和屬性可以向類中一樣快樂地使用@JvmStatic,@JvmField注解了。
一起來看個(gè)使用例子:

//在Kotlin接口中定義
interface Foo {
    companion object {
        @JvmField
        val answer: Int = 42

        @JvmStatic
        fun sayHello() {
            println("Hello, world!")
        }
    }
}
//在Java代碼中調(diào)用
class TestFoo {
    public static void main(String[] args){
        System.out.println("Foo test: " + Foo.answer + " say: " + Foo.sayHello());
    }
}

五然走、支持可變參數(shù)的FunctionN接口

不知道大家是否還記得我之前幾篇文章深入研究過Lambda表達(dá)式整個(gè)運(yùn)行原理援制,其中就詳細(xì)講了關(guān)于Function系列的接口。因?yàn)槲覀冎繪ambda表達(dá)式最后會(huì)編譯成一個(gè)class類芍瑞,這個(gè)類會(huì)去繼承Kotlin中Lambda的抽象類(在kotlin.jvm.internal包中)并且實(shí)現(xiàn)一個(gè)Function0...FunctionN(在kotlin.jvm.functions包中)的接口(這個(gè)N是根據(jù)lambda表達(dá)式傳入?yún)?shù)的個(gè)數(shù)決定的,目前接口N的取值為 0 <= N <= 22,也就是lambda表達(dá)式中函數(shù)傳入的參數(shù)最多也只能是22個(gè))晨仑,這個(gè)Lambda抽象類是實(shí)現(xiàn)了FunctionBase接口,該接口中有兩個(gè)方法一個(gè)是getArity()獲取lambda參數(shù)的元數(shù)啄巧,toString()實(shí)際上就是打印出Lambda表達(dá)式類型字符串寻歧,獲取Lambda表達(dá)式類型字符串是通過Java中Reflection類反射來實(shí)現(xiàn)的。FunctionBase接口繼承了Function,Serializable接口秩仆。(具體詳細(xì)內(nèi)容請(qǐng)參考我的這篇文章: 淺談Kotlin語法篇之lambda編譯成字節(jié)碼過程完全解析(七)

由上面分析得到N取值范圍是0 <= N <= 22码泛,要是此時(shí)有個(gè)lambda表達(dá)式函數(shù)參數(shù)個(gè)數(shù)是23個(gè)也就是大于22個(gè)時(shí)候該怎么辦?不就玩不轉(zhuǎn)了嗎澄耍? 雖然大于22個(gè)參數(shù)場景很少很少噪珊,但是這始終算是一個(gè)缺陷。所以這次Kotlin 1.3直接就徹底抹平這個(gè)缺陷齐莲,增加了FunctionN接口支持傳入的是可變長參數(shù)列表痢站,也就是支持任意個(gè)數(shù)參數(shù),這樣擴(kuò)展性就更強(qiáng)了选酗。

//官方源碼定義
interface FunctionN<out R> : Function<R>, FunctionBase<R> {
    /**
     * 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//可以看到這里接收是一個(gè)vararg 可變長參數(shù)阵难,支持任意個(gè)數(shù)的lambda表達(dá)式參數(shù),再也不用擔(dān)心超過22個(gè)參數(shù)該怎么辦了芒填。

    /**
     * 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中,注解類可以嵌套注解類殿衰、接口以及伴生對(duì)象.關(guān)于Kotlin中的注解和反射還沒有詳細(xì)深入研究過朱庆,這個(gè)暫且放一放,等到研究注解時(shí)候闷祥,會(huì)再次探討有關(guān)注解類嵌套的問題娱颊。

annotation class Foo {
    enum class Direction { UP, DOWN, LEFT, RIGHT }
    
    annotation class Bar

    companion object {
        fun foo(): Int = 42
        val bar: Int = 42
    }
}

七、結(jié)語

到這里Kotlin1.3新特性相關(guān)的內(nèi)容就結(jié)束凯砍。下面將會(huì)繼續(xù)深入研究下Kotlin 1.3中的inline class(主要是以翻譯國外優(yōu)秀文章為主)箱硕。然后就是去深入研究大家一直期待的協(xié)程和ktor框架,并把最終研究成果以文章的形式共享給大家悟衩。歡迎關(guān)注颅痊,會(huì)一直持續(xù)更新下去~~~

Kotlin系列文章,歡迎查看:

原創(chuàng)系列:

翻譯系列:

實(shí)戰(zhàn)系列:

qrcode_for_gh_109398d5e616_430.jpg

歡迎關(guān)注Kotlin開發(fā)者聯(lián)盟纽门,這里有最新Kotlin技術(shù)文章,每周會(huì)不定期翻譯一篇Kotlin國外技術(shù)文章营罢。如果你也喜歡Kotlin赏陵,歡迎加入我們~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市饲漾,隨后出現(xiàn)的幾起案子蝙搔,更是在濱河造成了極大的恐慌,老刑警劉巖考传,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吃型,死亡現(xiàn)場離奇詭異,居然都是意外死亡僚楞,警方通過查閱死者的電腦和手機(jī)勤晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泉褐,“玉大人赐写,你說我怎么就攤上這事∧ぴ撸” “怎么了挺邀?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跳座。 經(jīng)常有香客問我端铛,道長,這世上最難降的妖魔是什么躺坟? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮乳蓄,結(jié)果婚禮上咪橙,老公的妹妹穿的比我還像新娘。我一直安慰自己虚倒,他們只是感情好美侦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魂奥,像睡著了一般菠剩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耻煤,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天具壮,我揣著相機(jī)與錄音准颓,去河邊找鬼。 笑死棺妓,一個(gè)胖子當(dāng)著我的面吹牛攘已,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怜跑,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼样勃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了性芬?” 一聲冷哼從身側(cè)響起峡眶,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎植锉,沒想到半個(gè)月后辫樱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汽煮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年搏熄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暇赤。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡心例,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鞋囊,到底是詐尸還是另有隱情止后,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布溜腐,位于F島的核電站译株,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挺益。R本人自食惡果不足惜歉糜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望望众。 院中可真熱鬧匪补,春花似錦、人聲如沸烂翰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘耿。三九已至踊兜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佳恬,已是汗流浹背捏境。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工于游, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人典蝌。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓曙砂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骏掀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸠澈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容