Kotlin Tips——《Kotlin in Action》記錄

不重要的廢話

前段時間看了一遍《Programming Kotlin》粪狼,主要目的是想提高自己的英文閱讀能力,能力提高了沒有不知道任岸,反正回想起來再榄,書中的內(nèi)容啥也沒記住。現(xiàn)在在看《Kotlin in Action》享潜,想著不能再像之前了困鸥,要記錄點東西,本篇文章算是《Kotlin in Action》的讀書筆記剑按。這里我要順便夸夸《Kotlin in Action》這本書了疾就,這本書的兩位作者都是Kotlin語言的核心開發(fā)人員,內(nèi)容深入淺出艺蝴,大大增加了我對Kotlin語言的理解猬腰。

本篇記錄的都是一些tips,相對比較簡短猜敢,如果有看不懂的姑荷,語法相關(guān)的問題盒延,可以去Kotlin語言中文站查看相關(guān)語法;其他的問題可以直接查看《Kotlin in Action》這本書(中文版《Kotlin實戰(zhàn)》也已經(jīng)出版)鼠冕,或者搜索給出的關(guān)鍵詞進一步學(xué)習(xí)添寺,或者給我留言。文章中很多地方給出了對應(yīng)的英文懈费,不是為了裝13畦贸,因為很多內(nèi)容不好翻譯,未免言不達意楞捂,給出相應(yīng)的英文薄坏,也方便在《Kotlin in Action》中查找相關(guān)內(nèi)容。


方法(method)和函數(shù)(function):函數(shù) 是可以通過其名稱調(diào)用的一段代碼寨闹。方法 是與類實例關(guān)聯(lián)的 方法胶坠。簡單來說,在類內(nèi)部的函數(shù)稱為方法繁堡。Java中只有方法沈善,Kotlin中既有函數(shù)也有方法。為了表述方便椭蹄,Kotlin中的函數(shù)和方法闻牡,在本文中統(tǒng)稱為函數(shù)。

Kotlin Basics

  1. 聲明和表達式(statements and expressions):兩者的區(qū)別在于绳矩,表達式有返回值罩润,可以用在另一個表達式中,而聲明沒有返回值翼馆。在Kotlin中割以,除了循環(huán)(for,do应媚,while)外严沥,其它流程控制結(jié)構(gòu)(像是if,when)都是表達式中姜。另一方面消玄,賦值在Java中是表達式,但是在Kotlin中則是聲明丢胚,這避免了比較和賦值之間的困惑翩瓜。(This helps avoid confusion between comparisons and assignments, which is a common source of mistakes.)
  2. when表達式:when相比于Java中的switch更加的強大。switch的分支必須使用常量(enum嗜桌,string奥溺,number literal)辞色,而when的分支可以是任意對象或者是布爾表達式( boolean expression)骨宠,如果是對象的話則進行相等判斷(equality check)浮定。
  3. range and progression:如果可以遍歷所有range中的值的話,這樣的range稱為progression(有界的非無窮)层亿。任何實現(xiàn)了Comparable接口的類都可以用于創(chuàng)建range桦卒。range的范圍可以是無窮的,這樣的range不能遍歷匿又,但是可以判斷一個對象是否包含在其中(例如 "Kotlin" in "Java".."Scala" )方灾。

Function

  1. 擴展函數(shù)(extension function):顯然擴展函數(shù)不能破壞類的封裝,不能訪問類中 private 和 protected 的成員碌更。
  2. 擴展函數(shù)是靜態(tài)綁定:擴展函數(shù)在底層其實是類的靜態(tài)方法裕偿,屬于靜態(tài)綁定(static binding),不能重寫(override)痛单,也沒有多態(tài)嘿棘。
  3. 擴展函數(shù)和成員函數(shù):如果擴展函數(shù)和成員函數(shù)的簽名一樣的話,則優(yōu)先調(diào)用成員函數(shù)旭绒。注意鸟妙,如果在類中增加一個和擴展函數(shù)有一樣簽名的成員函數(shù),則會調(diào)用該成員函數(shù)挥吵,這樣會改變原來的意義重父。( If you add a member function with the same signature as an extension function that a client of your class has defined, and they then recompile their code, it will change its meaning and start referring to the new member function.)

Class,Object and Interface

  1. 類的默認特性:Kotlin在一些類特性的默認選擇上跟Java截然相反忽匈,這體現(xiàn)出Kotlin的一些設(shè)計理念房午。類是open還是final:默認final; 可見性:默認public丹允;內(nèi)部類還是嵌套類:默認嵌套類歪沃,也就是不含外部類的引用。(Inner and nested class: nested by default.)
  2. 擴展函數(shù)/擴展類的可見性:對于擴展函數(shù)/擴展類嫌松,它們的可見性只能比擴展接收者類/被擴展類的可見性保持一致或縮小沪曙,不能擴大,這是為了保證當(dāng)擴展函數(shù)/擴展類可訪問時萎羔,所有涉及的類都可以訪問液走。(This is a case of a general rule that requires all types used in the list of base types and type parameters of a class, or the signature of a method, to be as visible as the class or method itself. This rule ensures that you always have access to all types you might need to invoke the function or extend a class. )
internal open class TalkativeButton

//error! TalkativeButton是internal的,但是giveSpeech卻是public的
fun TalkativeButton.giveSpeech() {
}
  1. 伴生對象(companion object):伴生對象可以實現(xiàn)接口贾陷,并且可以使用包含類的類名作為該接口的實例缘眶。(you can use the name of the containing class directly as an instance of an object implementing the interface.)
interface JSONFactory<T> {
    fun fromJSON(jsonText: String): T 
}

class Person(val name: String) {
    companion object : JSONFactory<Person> {
        override fun fromJSON(jsonText: String): Person = ... 
    }
}

fun loadFromJSON<T>(factory: JSONFactory<T>): T {
... 
}

loadFromJSON(Person)//傳入的是Person的伴生對象,只是可以使用Person這個類名去代表
  1. 伴生對象上的擴展函數(shù):可以在伴生對象上定義擴展函數(shù)髓废。譬如巷懈,類C有伴生對象companion object,在C.Companion上定義擴展函數(shù)func慌洪,可以這樣調(diào)用它C.func()顶燕。那為什么不直接把函數(shù)func定義在伴生對象內(nèi)部呢凑保?不是能達到一樣的效果(所謂一樣的效果是指在類C上像調(diào)用靜態(tài)方法那樣調(diào)用func),大概是為了“代碼整潔之道”涌攻,不想在C類和其伴生對象中包含一些“不相關(guān)”的代碼欧引?

Lambda expression

  1. 捕獲變量(capturing variables):lambda表達式可以捕獲其所在作用域的val和var。一般的恳谎,一個局部變量的生命周期跟聲明它的函數(shù)是一致的芝此,但是當(dāng)它被lambda表達式捕獲時,它會被存儲在lambda表達式內(nèi)因痛,以便之后使用婚苹。當(dāng)捕獲的是val時,該變量會直接存儲在lambda表達式內(nèi)部鸵膏;當(dāng)捕獲的是var時租副,會創(chuàng)建一個包含此變量包裝類,然后lambda表達式會存儲指向該包裝類對象的引用较性,以便之后可以改變該變量用僧。之所以在Kotlin中可以捕獲非final變量(var),就是因為Kotlin在底層實現(xiàn)上赞咙,使用了我們經(jīng)常在Java中使用的小技巧:聲明一個final的包裝類對象责循,包裝類中含有要捕獲的可變變量。

  2. SAM轉(zhuǎn)換:可以把lambda表達式作為參數(shù)傳遞給一個需要SAM(single abstract method)接口的Java方法攀操,編譯器會幫我們生成一個實現(xiàn)該SAM接口的匿名類的對象院仿,如果lambda表達式中沒有捕獲任何變量,那么只會存在一個該SAM接口的對象速和,每次調(diào)用會重用該對象歹垫;如果lambda表達式中捕獲了變量,那么不可能再重用對象颠放,每次方法調(diào)用都會產(chǎn)生一個新的接口對象排惨。

 /* Java */
void postponeComputation(int delay, Runnable computation);

/* Kotlin */
/* 沒有捕獲變量的lambda表達式,只存在一個此Runnable接口的對象
每次調(diào)用postponeComputation都重用該對象 */
postponeComputation(1000) { 
    println(42)
} 

//以上代碼等效實現(xiàn)如下
val runnable = Runnable { println(42) } 
postponeComputation(1000, runnable) //每次調(diào)用都使用runnable變量

/* 捕獲變量的lambda表達式碰凶,
每次handleComputation調(diào)用都會創(chuàng)建一個新對象以包含捕獲變量 */
fun handleComputation(id: String) {
    postponeComputation(1000) { 
        println(id) 
    }
}
  1. lambda表達式實現(xiàn)的細節(jié):除了內(nèi)聯(lián)的lambda表達式暮芭,其它lambda表達式在底層上都被編譯成了匿名類(以Java 8為目標(biāo)平臺的,可以避免為每個lambda表達式編譯一個單獨的class文件)欲低。如果lambda表達式捕獲了變量辕宏,匿名類會包含每個捕獲變量的域,并且每次調(diào)用方法都會生成一個新的匿名類的對象砾莱。如果lambda表達式?jīng)]有捕獲變量瑞筐,則只有一個匿名類的對象。匿名類的名字(對于我們而言是匿名類腊瑟,對于編譯器來說還是需要類名的)是lambda表達式所在函數(shù)的名字再加上后綴聚假,例如块蚌,HandleComputation$1。如果decompile字節(jié)碼魔策,會有類似如下的代碼:
//捕獲的id變量,會有對應(yīng)的域
class HandleComputation$1(val id: String) : Runnable { 
    override fun run() {
        println(id)
    }
}

fun handleComputation(id: String) {
    postponeComputation(1000, HandleComputation$1(id))
} 

以上說的這些只是lambda表達式用于SAM轉(zhuǎn)換時底層的實現(xiàn)細節(jié)河胎,Kotlin本身的lambda表達式(除了內(nèi)聯(lián)的之外)都會被編譯成實現(xiàn)Function0(沒有參數(shù))闯袒,F(xiàn)unction1(有一個參數(shù)),F(xiàn)unction2等接口的類(詳見本文最后一條tip)游岳。這與上面所說的實現(xiàn)細節(jié)是類似的政敢,都是編譯成了匿名類,只是匿名類實現(xiàn)的接口不同胚迫。

  1. SAM構(gòu)造器(SAM constructor):SAM構(gòu)造器是編譯器生成的函數(shù)喷户,可以用來顯式地把一個lambda表達式轉(zhuǎn)換為SAM接口的實例》枚停可以用在返回SAM接口的方法中褪尝,或者把SAM接口對象存儲在一個變量中,等等:
//返回SAM接口
fun createAllDoneRunnable(): Runnable{
    return Runnable { 
        println("All done!") 
    }
}

//變量存儲SAM接口對象
val listener = OnClickListener { view ->
    val text = when (view.id) {
        R.id.button1 -> "First button"
        R.id.button2 -> "Second button"
        else -> "Unknown button"
    }
    toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
  1. lambda表達式中的this:在lambda表達式內(nèi)不能使用this期犬,因為lambda表達式其實是個匿名類的對象河哑。(Note that there's no this in a lambda as there is in an anonymous object: there's no way to refer to the anonymous class instance into which the lambda is converted.)在編譯器看來lambda表達式只是一段代碼塊,不是一個對象龟虎,因此不能使用this來指代璃谨。如果在lambda表達式里面使用this,也是指向的包含lambda表達式的類的對象鲤妥。如果確實需要在lambda表達式中使用this指代其本身佳吞,可以使用對象表達式
  2. 帶接收者的lambda表達式(lambdas with receiver):帶接收者的lambda表達式棉安,可以使用this底扳,指代其接收者,當(dāng)然也可以省略贡耽。

Sequence

  1. 序列(sequence):序列提供了懶集合操作(lazy collection operation)的能力花盐。集合上的操作是即刻完成的,會創(chuàng)建臨時的集合來存儲每一步操作的中間結(jié)果菇爪,而序列則是懶操作的算芯,不會創(chuàng)建臨時集合。
  2. 中間操作和終端操作(intermediate and terminal operation):序列上的操作分為中間操作(intermediate operation)和終端操作(terminal operation)凳宙,中間操作返回一個序列熙揍,而終端操作返回一個結(jié)果。(An intermediate operation returns another sequence, which knows how to transform the elements of the original sequence. A terminal operation returns a result, which may be a collection, an element, a number, or any other object that’s somehow obtained by the sequence of transformations of the initial collection.) 中間操作總是“懶”的氏涩,只有終端操作才會觸發(fā)所有的中間操作執(zhí)行届囚。
序列上的中間操作和終端操作
  1. 序列上的操作:集合上的操作與序列上的操作是不同的
listOf(1, 2, 3, 4).asSequence()
    .map { it * it }.find { it > 3 }
集合和序列上的操作有梆,即時的和懶的

Higher-order functions

  1. 內(nèi)聯(lián)(inline):集合上的操作(例如filter,map等)大都是inline的意系,因此傳入的lambda表達式不會產(chǎn)生匿名類泥耀,會編譯成內(nèi)聯(lián)的,但是蛔添,集合上的每步操作都會產(chǎn)生臨時集合痰催,如果集合包含的元素很多,這將會是很大的開銷迎瞧,可以考慮轉(zhuǎn)換為序列夸溶。序列上的操作不是inline的,會生成匿名類對象凶硅,但是不會有臨時序列缝裁。一般來說,如果集合不是特別大足绅,沒有必要使用序列捷绑,使用集合速度更快。
  2. 非局部返回(non-local return):在內(nèi)聯(lián)的lambda表達式中氢妈,return 會從包含該lambda表達式的函數(shù)中返回胎食,而不是lambda表達式本身。這稱為非局部返回允懂。如果需要從lambda表達式返回厕怜,需要使用帶標(biāo)簽的返回(return with a label)。一般情況下并不需要從lambda表達式中返回蕾总,lambda表達式最后一個表達式的值就是lambda表達式的值粥航。
  3. 匿名函數(shù)(anonymous function)return 會從最接近的使用fun定義的函數(shù)中返回。(return returns from the closest function declared using the fun keyword)顯然匿名函數(shù)會從自身返回生百,這與lambda表達式不同递雀。如果代碼塊中有多處退出點,使用匿名函數(shù)更加方便蚀浆。(You can use them if you need to write a block of code with multiple exit points.)
從最近的fun中返回
  1. 匿名函數(shù)就是lambda表達式:盡管匿名函數(shù)看上去像是普通的函數(shù)缀程,但是它們只是lambda表達式的另外一種語法形式,關(guān)于lambda表達式的實現(xiàn)的細節(jié)以及它們是如何內(nèi)聯(lián)的市俊,這些對于匿名函數(shù)仍然適用杨凑。(Note that despite the fact that an anonymous function looks similar to a regular function declaration, it's another syntactic form of a lambda expression. The discussion of how lambda expressions are implemented and how they're inlined for inline functions applies to anonymous functions as well.)

Kotlin Type System

  1. 可空的類型參數(shù)(nullability of type parameter):默認情況下,所有泛型類和泛型方法的類型參數(shù)(也就是我們經(jīng)常用的那個 T)是可空的摆昧。Kotlin中用 "?" 標(biāo)記的類型是可以為null的撩满,而沒有 "?" 標(biāo)記的類型不能為null的,唯一的例外就是類型參數(shù)。
  2. Nothing:用于表示函數(shù)沒有返回或者說函數(shù)沒有正常結(jié)束(拋出異常等)伺帘。Nothing類型沒有值昭躺,只有用作函數(shù)的返回類型或者類型參數(shù)才有意義。
  3. 集合和數(shù)組(collections and arrays): 對于 collection interface 始終要記住伪嫁,只讀的集合不一定是不可變的领炫,也不總是線程安全的。(Read-only collections aren't necessarily immutable. Read-only collections aren't always thread-safe.
  4. Kotlin collections and Java:每一個Kotlin的集合都是對應(yīng)的Java集合接口的實例张咳。但是帝洪,每個Java集合接口都在Kotlin中有兩種表示:只讀的和可變的。(Every Kotlin collection is an instance of the corresponding Java collection interface. But every Java collection interface has two representations in Kotlin: a read-only one and a mutable one.)
Kotlin集合接口的繼承結(jié)構(gòu)

Kotlin中的集合接口的基本繼承結(jié)構(gòu)跟Java中的集合是一樣的晶伦。(As you can see, the basic structure of the Kotlin read-only and mutable interfaces is parallel to the structure of the Java collection interfaces in the java.util package. )Kotlin中的mutable interface都繼承于相應(yīng)的read-only interface碟狞。

圖中的ArrayList和HashSet是Java中的類啄枕。在Kotlin中它們被分別看做 MutableList 和 MutableSet 的子類婚陪。對于其它的Java集合類(像是LinkedList, SortedSet 等)也是一樣的频祝。通過這種方式泌参,Kotlin的集合既和Java的集合兼容,又區(qū)分開了只讀的和可變的常空。

Collection type Read-only Mutable
List listOf() arrayListOf()
Set setOf() hashSetOf(), linkedSetOf(), sortedSetOf()
Map mapOf() hashMapOf(), linkedMapOf(), sortedMapOf()

但是沽一,只有在Kotlin中才能保證一個集合是只讀的,如果將一個只讀集合傳遞給Java方法漓糙,那么沒有什么能夠阻止Java改變該集合铣缠,甚至是破壞空類型安全(例如向集合中添加null),所以昆禽,我們自己必須小心控制(確定Java方法是否會改變集合蝗蛙,以傳入正確的Kotlin集合類型;驗證被Java改變的集合的類型等)醉鳖。
Kotlin沒有構(gòu)建自己的集合類捡硅,仍然使用了Java中的集合類。Kotlin是通過只讀的集合接口來保證集合是只讀的盗棵,在底層實現(xiàn)上仍然是使用了Java中對應(yīng)的可變集合壮韭。

  1. 集合的平臺類型(Collections as platform types):Java中的集合在Kotlin中有兩種表示:只讀的和可變的。再加上是否為可空類型纹因,因此喷屋,在Kotlin中重寫或?qū)崿F(xiàn)Java中含有集合類型的方法,你需要根據(jù)以下情況選擇合適Kotlin類型:
    • 集合是否為空瞭恰。
    • 集合中的元素是否為空逼蒙。
    • 是否需要修改集合。(read-only or mutable)

Operator overloading and other conventions

  1. 約定(conventions):在Java中實現(xiàn)了Iterable接口的類可以用在for循環(huán)中,Kotlin也有類似的語言特性是牢,并且更加方便僵井。與Java需要實現(xiàn)特定接口不同,Kotlin通過綁定特定名稱的函數(shù)來實現(xiàn)類似的特性驳棱,這種技術(shù)稱為約定批什,Kotlin中的運算符重載(operator overloading)就是使用這種技術(shù)。例如社搅,如果一個類上定義了名為plus的函數(shù)驻债,那么就可以按 約定 在這個類的對象上使用 + 運算符。
  2. rangeTo:前面說過任何實現(xiàn)了Comparable接口的類都可以用于創(chuàng)建range形葬。這是因為Kotlin標(biāo)準(zhǔn)庫定義了
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

有了以上定義合呐,我們可以創(chuàng)建一個range,并且判斷一個變量是否包含(in)在這個range中笙以,但是淌实,不能在for循環(huán)中遍歷這個range,因為返回的 ClosedRange<T> 沒有定義iterator猖腕,要想遍歷還需要在 ClosedRange<T> 上實現(xiàn)iterator

//LocalDate類是Java 8標(biāo)準(zhǔn)庫中的類拆祈,實現(xiàn)了Comparable接口
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
    object : Iterator<LocalDate> {
        var current = start
        override fun hasNext() =
            current <= endInclusive
        override fun next() = current.apply { 
            current = plusDays(1)
        }
    }
    
>>> val newYear = LocalDate.ofYearDay(2017, 1)
>>> val daysOff = newYear.minusDays(1)..newYear
>>> for (dayOff in daysOff) { println(dayOff) }
2016-12-31
2017-01-01
  1. in操作符(in operator):in操作符不僅僅可以用在range上,任意類只要實現(xiàn)了contains函數(shù)都可以使用in倘感,例如:
data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
        p.y in upperLeft.y until lowerRight.y
}
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect)
true
  1. 委托屬性(delegated properties)
class Foo {
    var c: Type by MyDelegate()
}

//編譯器生成類似這樣的代碼
class Foo {
    private val <delegate> = MyDelegate()
    
    var c: Type
    set(value: Type) = <delegate>.setValue(c, <property>, value)
    get() = <delegate>.getValue(c, <property>)
}
委托代替屬性訪問被調(diào)用

Generics

  1. 原生類型(raw type):不存在的放坏!Kotlin中的泛型必須指定類型參數(shù)(type argument)纽哥,可以是顯式指定或者是編譯器推斷竞慢。如果想實現(xiàn)類似Java原生類型那樣的功能纳令,可以使用星投影蘸嘶。
  2. 標(biāo)記類型參數(shù)為非空的(making type parameters non-null):如果不對類型參數(shù)做限制锅必,那么它可以是可空的類型捂齐。如果想保證類型參數(shù)非空汉操,需要指定一個非空的上界坪蚁,例如:
class Processor<T : Any> { 
    fun process(value: T) {
        value.hashCode()
    }
}
  1. 具體化(reified):reified可以具體化類型參數(shù)余素,奧秘在于inline而不是reified豹休。因為函數(shù)定義為內(nèi)聯(lián)的,編譯器將字節(jié)碼插入到每次函數(shù)調(diào)用的地方桨吊,生成的字節(jié)碼指向了具體的類型威根,而不是類型參數(shù),所以編譯器知道類型參數(shù)的具體類型视乐,不受類型擦除的影響洛搀。
  2. 具體化類型參數(shù)的限制(restrictions on reified type parameters)

可以:

  • 用于類型檢測和轉(zhuǎn)型(is , !is , as , as?)
  • 使用Kotlin反射(::class)
  • 獲取對應(yīng)的 java.lang.Class (::class.java)
  • 作為類型參數(shù)調(diào)用別的函數(shù)

不可以:

  • 創(chuàng)建類型參數(shù)類的實例(new instance)
  • 調(diào)用類型參數(shù)類伴生對象的函數(shù)(Call methods on the companion object of the type parameter class)
  • 使用非具體化的類型參數(shù)調(diào)用具體化類型參數(shù)的函數(shù)(Use a non-reified type parameter as a type argument when calling a function with a reified type parameter)
  • 在類,屬性佑淀,非內(nèi)聯(lián)函數(shù)上使用reified

有的是限制是因為內(nèi)在原理留美,有的限制是因為現(xiàn)有的實現(xiàn),可能會在Kotlin之后的版本中放松。(Some are inherent to the concept, and others are determined by the current implementation and may be relaxed in future versions of Kotlin.)其實我沒有發(fā)現(xiàn)哪一條是因為現(xiàn)有實現(xiàn)而來的限制谎砾,我怎么覺得每一條都是因為內(nèi)在原理逢倍。

  1. 子類和子類型(subclass and subtype):A type B is a subtype of a type A if you can use the value of the type B whenever a value of the type A is required. 子類一定是子類型,子類型不一定是子類景图。例如较雕,在Kotlin中,String 類是String? 類的子類型挚币,但是不是其子類亮蒋。還有一種情況就是在泛型類的型變中。我另外一篇文章有詳細的介紹:Java和Kotlin中泛型的協(xié)變妆毕、逆變和不變慎玖。
  2. 星投影(star projection):當(dāng)你不知道泛型類的類型參數(shù)是什么或者是什么并不重要時,可以使用星投影的語法笛粘。星投影對應(yīng)于Java中的 無界通配符 趁怔,即Kotlin中的MyType<*> 對應(yīng)于Java中的 MyType<?>。一個使用星投影的例子闰蛔,在Map中存儲泛型類對象:
interface FieldValidator<in T> { 
    fun validate(input: T): Boolean
}

object DefaultStringValidator : FieldValidator<String> { 
    override fun validate(input: String) = input.isNotEmpty()
}

object DefaultIntValidator : FieldValidator<Int>  { 
    override fun validate(input: Int) = input >= 0
}

object Validators {
    private val validators =
        mutableMapOf<KClass<*>, FieldValidator<*>>()
        
    //保證了類型安全
    fun <T: Any> registerValidator(
    kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
        validators[kClass] = fieldValidator
    }
    
    //轉(zhuǎn)型不會有問題
    @Suppress("UNCHECKED_CAST")
    operator fun <T: Any> get(kClass: KClass<T>): FieldValidator<T> =
        validators[kClass] as? FieldValidator<T>
            ?: throw IllegalArgumentException( "No validator for ${kClass.simpleName}")
}

>>> Validators.registerValidator(String::class, DefaultStringValidator)
>>> Validators.registerValidator(Int::class, DefaultIntValidator)
>>> println(Validators[String::class].validate("Kotlin"))
true
>>> println(Validators[Int::class].validate(42))
true

Annotations and Reflection

  1. 注解的目標(biāo)(annotation targets):很多時候痕钢,Kotlin中單一的聲明對應(yīng)于Java中多個聲明图柏。例如序六,Kotlin中的property對應(yīng)于Java中的 field, getter, and possibly a setter, as well as the parameters of the accessors. A property declared in the primary constructor has one more corresponding element: the constructor parameter. 因此需要標(biāo)明注解的是哪個元素,這稱為使用目標(biāo)(use-site target)聲明蚤吹,如下:
使用目標(biāo)的語法

使用Java中定義的注解去注解Kotlin中的屬性例诀,默認是應(yīng)用在對應(yīng)的field上的,可以使用下面的使用目標(biāo)進行指定:

  • property (Java annotations can't be applied with this use-target);
  • field (the field generated for the property);
  • get (property getter);
  • set (property setter);
  • receiver (receiver parameter of an extension function or property);
  • param (constructor parameter);
  • setparam (property setter parameter);
  • delegate (the field storing the delegate instance for a delegated property);
  • file (the class containing top-level functions and properties declared in the file).
  1. 使用注解控制Java API(Controlling the Java API with annotations):Kotlin中提供了很多注解來控制Kotlin中的聲明怎么編譯成Java字節(jié)碼裁着,以及怎樣被Java調(diào)用繁涂。一些Java中的keywords轉(zhuǎn)換成了Kotlin中的注解,例如@Volatile@Strictfp 就是Java中 volatilestrictfp 的代替二驰。還有一些控制Kotlin中的聲明對Java調(diào)用者的可見性扔罪,例如:
    • @JvmName changes the name of a Java method or field generated from a Kotlin declarations;
    • @JvmStatic can be applied to methods of an object declaration or a companion object to expose thom as static Java methods;
    • @JvmOverloads instructs the Kotlin compiler to generate overloads for a method which has default parameter values;
    • @JvmField can be applied to a property to expose that property as a public Java field with no getters or setters.
  2. Kotlin反射的接口:所有的表達式都可以被注解,因此反射接口都繼承自KAnnotatedElement桶雀。KClass代表class和object矿酵。KProperty代表任意屬性全肮,它的子類KMutableProperty代表可變屬性(var)棘捣。KPropertyKMutableProperty內(nèi)部還定義了GetterSetter 接口辜腺,當(dāng)需要把 property accessors 當(dāng)做函數(shù)時可以使用。圖中沒有顯示KProperty0接口评疗,它可以用來代表頂層屬性(top-level property)。
Kotlin反射接口的繼承結(jié)構(gòu)

與Java的不同

  1. 可變參數(shù)(vararg):與Java中使用三個點(...)不同百匆,Kotlin使用vararg來表示 可變參數(shù)邑彪。另一個不同是胧华,在Java中可以直接向 可變參數(shù) 傳遞一個數(shù)組,但是在Kotlin中有巧,你必須顯式地把數(shù)組“展開”篮迎,這稱為伸展操作符(spread operator)示姿,實際就是在數(shù)組前加上 * 號,例如:
fun main(args: Array<String>) {
    val list = listOf("args: ", *args)
    println(list)
}
  1. 可見性:在Java中可以在同一個包內(nèi)訪問protected成員岂傲,但是在Kotlin中不可以镊掖。Kotlin中褂痰,protected成員只對該類及其子類可見缩歪。另一點不同是,Kotlin中的外部類不能訪問 內(nèi)部類/嵌套類 的private成員主籍。但是在Java中可以骗污。
  2. 對象表達式(object expression):對象表達式取代了Java中的匿名內(nèi)部類需忿,但是可以用來實現(xiàn)多個接口蜡歹。還有一點與Java不同的是月而,在Kotlin的對象表達式的內(nèi)部可以訪問創(chuàng)建它的函數(shù)的變量议纯,不僅限于final變量瞻凤。(Just as with Java's anonymous classes, code in an object expression can access the variables in the function where it was created. But unlike in Java, this isn’t restricted to final variables)
  3. 內(nèi)聯(lián)(inline):Java中的方法不支持inline,在Java中可以調(diào)用Kotlin中inline的函數(shù)肝集,但是這些函數(shù)并不會內(nèi)聯(lián)杏瞻。如果Kotlin中定義的函數(shù)是inline并且reified衙荐,那么Java不能調(diào)用這樣的函數(shù)忧吟,Java不支持方法內(nèi)聯(lián),自然也就不能具體化參數(shù)類型胸嘴。
  4. 注解(annotations):Java中的注解僅能注解類和方法聲明或類型,而Kotlin中的注解可以注解任意表達式和文件摧玫。(Note that unlike Java, Kotlin allows you to apply annotations to arbitrary expressions, and not only to class and method declarations or types. )
  5. 元注解(meta-annotation):元注解 @Retention 的默認值在Java中是 RetentionPolicy.CLASS 绑青,即注解保留在 .class 文件中闸婴,但是運行時不能獲刃罢А对竣;在Kotlin中是 RetentionPolicy.RUNTIME 否纬,即運行時可以使用蛋褥。一般來說烙心,Kotlin中不需要特別聲明 @Retention ,因為我們一般就是要使用 RetentionPolicy.RUNTIME溃论。

與Java互操作

  1. 命名參數(shù)(named argument):在Kotlin中調(diào)用Java的方法不能使用命名參數(shù)(包括JDK和Android Framework中的方法)钥勋。在 .class 文件中存儲參數(shù)名稱是從Java 8開始的一項可選特性辆苔。Kotlin兼容Java 6驻啤,因此編譯器不能識別命名參數(shù)骑冗,并把其匹配到方法的定義中。(Kotlin maintains compatibility with Java 6. As a result, the compiler can't recognize the parameter names used in your call and match them against the method definition.)
  2. 參數(shù)默認值(default parameter value):Java中沒有這個概念巧涧,因此從Java中調(diào)用Kotlin函數(shù)谤绳,必須使用所有參數(shù)袒哥。為方便起見堡称,也可以在Kotlin中定義函數(shù)時加上 @JvmOverloads 却紧,這樣編譯器會生成所有的 Java 重載方法钞艇。
  3. 頂層函數(shù)(top-level function):因為有頂層函數(shù)哩照,所以Kotlin不需要靜態(tài)工具類(static utility class)懒浮。但是由于在JVM中砚著,所有的代碼都必須在類中稽穆,因此,Kotlin中的頂層函數(shù)最終還是被編譯成了靜態(tài)工具類中的方法柱彻,類名是文件名(Utils.kt -> UtilsKt)哟楷。在Java中調(diào)用Kotlin的頂層函數(shù)否灾,即是調(diào)用這些靜態(tài)工具類中的方法墨技。想要修改這些靜態(tài)工具類的類名可以使用 @file:JvmName("NameYouWant") 注解(寫在文件的開始扣汪,package之前)。
  4. 擴展函數(shù)(extension function):在底層脐嫂,擴展函數(shù)僅是把接收者(receiver object)作為第一個參數(shù)的靜態(tài)方法。在Java中可以像調(diào)用頂層函數(shù)那樣調(diào)用擴展函數(shù)暗膜,只是將接收者作為第一個參數(shù)傳入鞭衩。
  5. 接口的默認實現(xiàn):Kotlin接口中定義的函數(shù)可以包含有默認實現(xiàn),但是Kotlin是兼容Java 6的聚磺,Java 6中不支持接口的默認實現(xiàn)的瘫寝,因此稠炬,Kotlin中帶有默認實現(xiàn)的接口被編譯成了普通接口和包含有接口實現(xiàn)的類的組合首启。(Therefore, it compiles each interface with default methods to a combination of a regular interface and a class containing the method bodies as static methods. The interface contains only declarations, and the class contains all the implementations as static methods. )所以說毅桃,在Java中還是需要實現(xiàn)Kotlin接口的所有方法,不論它在Kotlin中是否存在默認實現(xiàn)外厂。Kotlin現(xiàn)在可以生成Java 8的字節(jié)碼汁蝶,如果選擇Java 8作為target掖棉,則Kotlin接口的默認實現(xiàn)會編譯成像Java 8那樣膀估。
  6. 可見性:Kotlin中的private類(只能對當(dāng)前文件可見)被編譯成了包可見性察纯,因為Java中不允許一個類是private的饼记。而internal都被編譯成了public的(在字節(jié)碼層面)具则。這也就是為什么有的在Kotlin中不可見的類、函數(shù)在Java中反而可見低斋。
  7. 空安全:Java不支持空安全膊畴,Java中的類型會轉(zhuǎn)變成Kotlin中的平臺類型(platform type),平臺類型本質(zhì)上是沒有空信息的類型稠通,既可以當(dāng)做可空類型采记,也可以當(dāng)做非空類型唧龄。
Java類型在Kotlin中被表示為平臺類型
  1. 函數(shù)類型(function types):在底層既棺,函數(shù)類型都被聲明為了普通接口丸冕,像是Function0<R>(沒有參數(shù))薛窥,F(xiàn)unction1<P1, R>(有一個參數(shù))等等诅迷,每個接口中只包含一個 invoke 函數(shù)罢杉。在Java中調(diào)用Kotlin使用函數(shù)類型的函數(shù)(高階函數(shù))很簡單滩租,使用Java 8 的話,Java 8 中的lambda表達式自動轉(zhuǎn)換為了對應(yīng)的函數(shù)類型猎莲;使用Java 8 之前的版本益眉,需要傳入實現(xiàn)相應(yīng)接口匿名內(nèi)部類對象郭脂。
/* Kotlin declaration */
fun processTheAnswer(f: (Int) -> Int) { 
    println(f(42))
}

/* Java 8*/
processTheAnswer(number -> number + 1);

/* Java 8 之前的版本*/
processTheAnswer(new Function1<Integer, Integer>() {
    @Override
    public Integer invoke(Integer number){ 
        System.out.println(number);
        return number + 1;
    }
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末展鸡,一起剝皮案震驚了整個濱河市莹弊,隨后出現(xiàn)的幾起案子忍弛,更是在濱河造成了極大的恐慌考抄,老刑警劉巖川梅,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贫途,死亡現(xiàn)場離奇詭異丢早,居然都是意外死亡怨酝,警方通過查閱死者的電腦和手機凫碌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門盛险,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苦掘,“玉大人,你說我怎么就攤上這事惯驼∷钌” “怎么了说贝?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵乡恕,是天一觀的道長傲宜。 經(jīng)常有香客問我函卒,道長,這世上最難降的妖魔是什么躁愿? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任彤钟,我火速辦了婚禮跷叉,結(jié)果婚禮上云挟,老公的妹妹穿的比我還像新娘园欣。我一直安慰自己沸枯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窃诉,像睡著了一般飘痛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辅搬,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天介蛉,我揣著相機與錄音币旧,去河邊找鬼。 笑死巍虫,一個胖子當(dāng)著我的面吹牛占遥,可吹牛的內(nèi)容都是我干的瓦胎。 我是一名探鬼主播搔啊,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼负芋,長吁一口氣:“原來是場噩夢啊……” “哼旧蛾!你這毒婦竟也來了蚜点?” 一聲冷哼從身側(cè)響起绍绘,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤陪拘,失蹤者是張志新(化名)和其女友劉穎左刽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迄靠,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年吠式,在試婚紗的時候發(fā)現(xiàn)自己被綠了特占。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片是目。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胖笛,死狀恐怖长踊,靈堂內(nèi)的尸體忽然破棺而出身弊,到底是詐尸還是另有隱情阱佛,我是刑警寧澤凑术,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布淮逊,位于F島的核電站泄鹏,受9級特大地震影響郎任,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜备籽,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一舶治、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧车猬,春花似錦霉猛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铸磅。三九已至,卻和暖如春杭朱,著一層夾襖步出監(jiān)牢的瞬間阅仔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工弧械, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留八酒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓刃唐,卻偏偏與公主長得像羞迷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子画饥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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