簡述:
上接上篇文章,我們深入分析了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)類。那我們一起來看下本次提綱:
一担锤、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高亮提示
第二種: 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)圖一起興奮一下:
不知道大家在開發(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)系列:
- JetBrains開發(fā)者日見聞(二)之Kotlin1.3的新特性(Contract契約與協(xié)程篇)
- JetBrains開發(fā)者日見聞(一)之Kotlin/Native 嘗鮮篇
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(實(shí)踐篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(下篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(上篇)
- Kotlin的獨(dú)門秘籍Reified實(shí)化類型參數(shù)(下篇)
- 有關(guān)Kotlin屬性代理你需要知道的一切
- 淺談Kotlin中的Sequences源碼解析
- 淺談Kotlin中集合和函數(shù)式API完全解析-上篇
- 淺談Kotlin語法篇之lambda編譯成字節(jié)碼過程完全解析
- 淺談Kotlin語法篇之Lambda表達(dá)式完全解析
- 淺談Kotlin語法篇之?dāng)U展函數(shù)
- 淺談Kotlin語法篇之頂層函數(shù)局待、中綴調(diào)用斑响、解構(gòu)聲明
- 淺談Kotlin語法篇之如何讓函數(shù)更好地調(diào)用
- 淺談Kotlin語法篇之變量和常量
- 淺談Kotlin語法篇之基礎(chǔ)語法
翻譯系列:
- [譯]Kotlin的獨(dú)門秘籍Reified實(shí)化類型參數(shù)(上篇)
- [譯]Kotlin泛型中何時(shí)該用類型形參約束?
- [譯] 一個(gè)簡單方式教你記住Kotlin的形參和實(shí)參
- [譯]Kotlin中是應(yīng)該定義函數(shù)還是定義屬性?
- [譯]如何在你的Kotlin代碼中移除所有的!!(非空斷言)
- [譯]掌握Kotlin中的標(biāo)準(zhǔn)庫函數(shù): run菱属、with、let舰罚、also和apply
- [譯]有關(guān)Kotlin類型別名(typealias)你需要知道的一切
- [譯]Kotlin中是應(yīng)該使用序列(Sequences)還是集合(Lists)?
- [譯]Kotlin中的龜(List)兔(Sequence)賽跑
- [譯]Effective Kotlin系列之考慮使用靜態(tài)工廠方法替代構(gòu)造器
- [譯]Effective Kotlin系列之遇到多個(gè)構(gòu)造器參數(shù)要考慮使用構(gòu)建器
實(shí)戰(zhàn)系列:
- 用Kotlin擼一個(gè)圖片壓縮插件ImageSlimming-導(dǎo)學(xué)篇(一)
- 用Kotlin擼一個(gè)圖片壓縮插件-插件基礎(chǔ)篇(二)
- 用Kotlin擼一個(gè)圖片壓縮插件-實(shí)戰(zhàn)篇(三)
- 淺談Kotlin實(shí)戰(zhàn)篇之自定義View圖片圓角簡單應(yīng)用
歡迎關(guān)注Kotlin開發(fā)者聯(lián)盟纽门,這里有最新Kotlin技術(shù)文章,每周會(huì)不定期翻譯一篇Kotlin國外技術(shù)文章营罢。如果你也喜歡Kotlin赏陵,歡迎加入我們~~~