Jetbrains開發者日見聞(三)之Kotlin1.3新特性有哪些?
簡述: 上接上篇文章,我們深入分析了Kotlin1.3版本中的Contract契約的內容,那麼這篇文章將會繼續把Kotlin1.3新特性研究完畢。這篇文章還有個非常重要的點就是inline class 內聯類。關於內聯類的知識除了這篇文章會有介紹,後面馬上會翻譯幾篇有關Kotlin中的內聯類相關內容。只有一個目的徹底搞定Kotlin中的內聯類。那我們一起來看下本次提綱:
一、inline class內聯類(Experimental)
1、複習inline行內函數的作用
關於inline內聯相信大家都不陌生吧,在實際開發中我們經常會使用Kotlin中的inline行內函數。那大家還記得inline內聯作用嗎? 這裡再次複習下inline內聯的作用:
inline行內函數主要有兩大作用:
-
用於lambda表示式呼叫,降低Function系列物件例項建立的記憶體開銷,從而提高效能。宣告成行內函數的話,而是在呼叫的時把呼叫的方法給替換掉,可以降低很大的效能開銷。
-
另一個行內函數作用就是它能是泛型函式型別實參進行實化,在執行時能拿到型別實參的資訊。
2、為何需要內聯類
通過複習了inline函式的兩大作用,實際上內聯類存在意義和inline函式第一個作用有點像。有時候業務場景需要針對某種型別建立包裝器類。 但是,使用包裝器類避免不了去例項化這個包裝器類,但是這樣會帶來額外的建立物件的堆分配,它會引入執行時開銷。 此外,如果包裝型別是基礎型別的,效能損失是很糟糕的,因為基礎型別通常在執行時大大優化,而它們的包裝器沒有得到任何特殊處理。
那到底是什麼場景會需要內聯類呢? 實際上,在開發中有時候就基本資料型別外加變數名是無法完全表達某個欄位的含義,甚至還有可能造成歧義,這種場景就特別適合內聯類包裝器。是不是很抽象,那麼一起來看個例子。
- 案例一: Iterable擴充套件函式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()
}
複製程式碼
我們仔細分析下joinToString函式,它有很多個函式引數,其中讓人感到歧義就是前面三個: separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = ""
.有的人就會說不會有歧義啊,定義得很清楚很明白了,separator,prefix,postfix形參名明顯都有自己的含義啊。僅僅從joinToString這個函式的角度來看確實比較清晰。可是你有沒有想過外層呼叫者困惑呢。對於外部呼叫者前面三個引數都是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: 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<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)
}
複製程式碼
看到這裡是不是很多人覺得這樣實現有問題,雖然它能很好解決我們上述型別不明確的問題。但是卻引入一個更大問題,需要額外建立Speparator、Prefix、Postfix例項物件,帶來很多的記憶體開銷。從投入產出比來看,估計沒人這麼玩吧。這是因為inline class沒出來之前,但是如果inline class能把效能開銷降低到和直接使用String一樣的效能開銷,你還認為它是很差的方案嗎? 請接著往下看
第四種: Kotlin中inline class終極解決方案
針對上述問題,Kotlin提出inline class解決方案,就是從源頭上解決問題。一起來看看:
fun main(args: Array<String>) {
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 <T> Iterable<T>.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場景了。
3、內聯類的基本介紹
- 基本定義
inline class 為了解決包裝器類帶來額外效能開銷問題的一種特殊類。
- 基本結構
基本結構很簡單就是在普通class前面加上inline關鍵字
4、如何嘗鮮inline class
因為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的配置
<configuration>
<args>
<arg>-XXLanguage:+InlineClasses</arg>
</args>
</configuration>
複製程式碼
5、內聯類和typealias的區別
估計很多人都沒使用過typealias吧,如果還不瞭解typealias的話請參考我之前的這篇文章:[譯]有關Kotlin類型別名(typealias)你需要知道的一切. 其實上述那個問題還可以用typealias來改寫,但是你會發現是有點缺陷的,並沒有達到想要效果。可以給大家看下:
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)
}
複製程式碼
關於inline class和typealias有很大相同點不同點,相同點在於: 他們兩者看起來貌似都引入一種新型別,並且兩者都將在執行時表現為基礎型別。不同點在於: typealias僅僅是給基礎型別取了一個別名而已,而inline class是基礎型別一個包裝器類。換句話說inline class才是真正引入了一個新的型別,而typealias則沒有。
是不是還是有點抽象啊,來個例子你就明白了
typealias Token = String
inline class TokenWrapper(val value: String)
fun main(args: Array<String>) {
val token: Token = "r3huae03zdhreol38fdjhkdfd8df"//可以看出這裡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值
}
複製程式碼
6、內聯類的反編譯原始碼分析
通過反編譯分析,就能清楚明白為什麼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"
. 所以效能問題就不用擔心了。
7、內聯類使用限制
- 1、內聯類必須含有主構造器且構造器內參數個數有且僅有一個,形參只能是隻讀的(val修飾)。
- 2、內聯類不能含有init block
- 3、內聯類不能含有inner class
二、when表示式的使用優化
Kotlin1.3新特性對when表示式做一個寫法上的優化,為什麼這麼說呢?僅僅就是寫法上的優化,實際上什麼都沒做,一起來研究下。不知道大家在使用when表示式有沒有這樣感受(反正我是有過這樣的感受): 在when表示式作用域內,老天啊請賜我一個像lambda表示式中的一樣it例項物件指代吧。---來自眾多Kotlin開發者心聲。一起看下這個例子:
1、Anko庫原始碼中fillIntentArguments函式部分程式碼分析
@JvmStatic
private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
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團隊的大佬們對開發者的問題處理還是蠻積極的,馬上就優化這個問題。
2、1.3版本when表示式優化版本
官方到底是怎麼優化的呢? 那麼有的人就說了是不是像lambda表示式一樣賜予我們一個it指代呢。官方的回答是: NO. 一起再來看1.3版本的實現:
private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
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
}
}
複製程式碼
3、優化之後反編譯程式碼對比
- kotlin 1.3版本之前when表示式實現
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表示式使用反編譯程式碼
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表示式實現
fun main(args: Array<String>) {
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<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//可以看到這裡接收是一個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,歡迎加入我們~~~