Kotlin Tips——《Kotlin in Action》記錄

不重要的廢話

前段時間看了一遍《Programming Kotlin》,主要目的是想提高自己的英文閱讀能力,能力提高了沒有不知道,反正回想起來,書中的內容啥也沒記住。現在在看《Kotlin in Action》,想著不能再像之前了,要記錄點東西,本篇文章算是《Kotlin in Action》的讀書筆記。這里我要順便夸夸《Kotlin in Action》這本書了,這本書的兩位作者都是Kotlin語言的核心開發人員,內容深入淺出,大大增加了我對Kotlin語言的理解。

本篇記錄的都是一些tips,相對比較簡短,如果有看不懂的,語法相關的問題,可以去Kotlin語言中文站查看相關語法;其他的問題可以直接查看《Kotlin in Action》這本書(中文版《Kotlin實戰》也已經出版),或者搜索給出的關鍵詞進一步學習,或者給我留言。文章中很多地方給出了對應的英文,不是為了裝13,因為很多內容不好翻譯,未免言不達意,給出相應的英文,也方便在《Kotlin in Action》中查找相關內容。


方法(method)和函數(function):函數 是可以通過其名稱調用的一段代碼。方法 是與類實例關聯的 方法。簡單來說,在類內部的函數稱為方法。Java中只有方法,Kotlin中既有函數也有方法。為了表述方便,Kotlin中的函數和方法,在本文中統稱為函數。

Kotlin Basics

  1. 聲明和表達式(statements and expressions):兩者的區別在于,表達式有返回值,可以用在另一個表達式中,而聲明沒有返回值。在Kotlin中,除了循環(for,do,while)外,其它流程控制結構(像是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(有界的非無窮)。任何實現了Comparable接口的類都可以用于創建range。range的范圍可以是無窮的,這樣的range不能遍歷,但是可以判斷一個對象是否包含在其中(例如 "Kotlin" in "Java".."Scala" )。

Function

  1. 擴展函數(extension function):顯然擴展函數不能破壞類的封裝,不能訪問類中 private 和 protected 的成員。
  2. 擴展函數是靜態綁定:擴展函數在底層其實是類的靜態方法,屬于靜態綁定(static binding),不能重寫(override),也沒有多態。
  3. 擴展函數和成員函數:如果擴展函數和成員函數的簽名一樣的話,則優先調用成員函數。注意,如果在類中增加一個和擴展函數有一樣簽名的成員函數,則會調用該成員函數,這樣會改變原來的意義。( 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截然相反,這體現出Kotlin的一些設計理念。類是open還是final:默認final; 可見性:默認public;內部類還是嵌套類:默認嵌套類,也就是不含外部類的引用。(Inner and nested class: nested by default.)
  2. 擴展函數/擴展類的可見性:對于擴展函數/擴展類,它們的可見性只能比擴展接收者類/被擴展類的可見性保持一致或縮小,不能擴大,這是為了保證當擴展函數/擴展類可訪問時,所有涉及的類都可以訪問。(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):伴生對象可以實現接口,并且可以使用包含類的類名作為該接口的實例。(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. 伴生對象上的擴展函數:可以在伴生對象上定義擴展函數。譬如,類C有伴生對象companion object,在C.Companion上定義擴展函數func,可以這樣調用它C.func()。那為什么不直接把函數func定義在伴生對象內部呢?不是能達到一樣的效果(所謂一樣的效果是指在類C上像調用靜態方法那樣調用func),大概是為了“代碼整潔之道”,不想在C類和其伴生對象中包含一些“不相關”的代碼?

Lambda expression

  1. 捕獲變量(capturing variables):lambda表達式可以捕獲其所在作用域的val和var。一般的,一個局部變量的生命周期跟聲明它的函數是一致的,但是當它被lambda表達式捕獲時,它會被存儲在lambda表達式內,以便之后使用。當捕獲的是val時,該變量會直接存儲在lambda表達式內部;當捕獲的是var時,會創建一個包含此變量包裝類,然后lambda表達式會存儲指向該包裝類對象的引用,以便之后可以改變該變量。之所以在Kotlin中可以捕獲非final變量(var),就是因為Kotlin在底層實現上,使用了我們經常在Java中使用的小技巧:聲明一個final的包裝類對象,包裝類中含有要捕獲的可變變量。

  2. SAM轉換:可以把lambda表達式作為參數傳遞給一個需要SAM(single abstract method)接口的Java方法,編譯器會幫我們生成一個實現該SAM接口的匿名類的對象,如果lambda表達式中沒有捕獲任何變量,那么只會存在一個該SAM接口的對象,每次調用會重用該對象;如果lambda表達式中捕獲了變量,那么不可能再重用對象,每次方法調用都會產生一個新的接口對象。

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

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

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

/* 捕獲變量的lambda表達式,
每次handleComputation調用都會創建一個新對象以包含捕獲變量 */
fun handleComputation(id: String) {
    postponeComputation(1000) { 
        println(id) 
    }
}
  1. lambda表達式實現的細節:除了內聯的lambda表達式,其它lambda表達式在底層上都被編譯成了匿名類(以Java 8為目標平臺的,可以避免為每個lambda表達式編譯一個單獨的class文件)。如果lambda表達式捕獲了變量,匿名類會包含每個捕獲變量的域,并且每次調用方法都會生成一個新的匿名類的對象。如果lambda表達式沒有捕獲變量,則只有一個匿名類的對象。匿名類的名字(對于我們而言是匿名類,對于編譯器來說還是需要類名的)是lambda表達式所在函數的名字再加上后綴,例如,HandleComputation$1。如果decompile字節碼,會有類似如下的代碼:
//捕獲的id變量,會有對應的域
class HandleComputation$1(val id: String) : Runnable { 
    override fun run() {
        println(id)
    }
}

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

以上說的這些只是lambda表達式用于SAM轉換時底層的實現細節,Kotlin本身的lambda表達式(除了內聯的之外)都會被編譯成實現Function0(沒有參數),Function1(有一個參數),Function2等接口的類(詳見本文最后一條tip)。這與上面所說的實現細節是類似的,都是編譯成了匿名類,只是匿名類實現的接口不同。

  1. SAM構造器(SAM constructor):SAM構造器是編譯器生成的函數,可以用來顯式地把一個lambda表達式轉換為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表達式內不能使用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,指代其接收者,當然也可以省略。

Sequence

  1. 序列(sequence):序列提供了懶集合操作(lazy collection operation)的能力。集合上的操作是即刻完成的,會創建臨時的集合來存儲每一步操作的中間結果,而序列則是懶操作的,不會創建臨時集合。
  2. 中間操作和終端操作(intermediate and terminal operation):序列上的操作分為中間操作(intermediate operation)和終端操作(terminal operation),中間操作返回一個序列,而終端操作返回一個結果。(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.) 中間操作總是“懶”的,只有終端操作才會觸發所有的中間操作執行。
序列上的中間操作和終端操作
  1. 序列上的操作:集合上的操作與序列上的操作是不同的
listOf(1, 2, 3, 4).asSequence()
    .map { it * it }.find { it > 3 }
集合和序列上的操作,即時的和懶的

Higher-order functions

  1. 內聯(inline):集合上的操作(例如filter,map等)大都是inline的,因此傳入的lambda表達式不會產生匿名類,會編譯成內聯的,但是,集合上的每步操作都會產生臨時集合,如果集合包含的元素很多,這將會是很大的開銷,可以考慮轉換為序列。序列上的操作不是inline的,會生成匿名類對象,但是不會有臨時序列。一般來說,如果集合不是特別大,沒有必要使用序列,使用集合速度更快。
  2. 非局部返回(non-local return):在內聯的lambda表達式中,return 會從包含該lambda表達式的函數中返回,而不是lambda表達式本身。這稱為非局部返回。如果需要從lambda表達式返回,需要使用帶標簽的返回(return with a label)。一般情況下并不需要從lambda表達式中返回,lambda表達式最后一個表達式的值就是lambda表達式的值。
  3. 匿名函數(anonymous function)return 會從最接近的使用fun定義的函數中返回。(return returns from the closest function declared using the fun keyword)顯然匿名函數會從自身返回,這與lambda表達式不同。如果代碼塊中有多處退出點,使用匿名函數更加方便。(You can use them if you need to write a block of code with multiple exit points.)
從最近的fun中返回
  1. 匿名函數就是lambda表達式:盡管匿名函數看上去像是普通的函數,但是它們只是lambda表達式的另外一種語法形式,關于lambda表達式的實現的細節以及它們是如何內聯的,這些對于匿名函數仍然適用。(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. 可空的類型參數(nullability of type parameter):默認情況下,所有泛型類和泛型方法的類型參數(也就是我們經常用的那個 T)是可空的。Kotlin中用 "?" 標記的類型是可以為null的,而沒有 "?" 標記的類型不能為null的,唯一的例外就是類型參數。
  2. Nothing:用于表示函數沒有返回或者說函數沒有正常結束(拋出異常等)。Nothing類型沒有值,只有用作函數的返回類型或者類型參數才有意義。
  3. 集合和數組(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的集合都是對應的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集合接口的繼承結構

Kotlin中的集合接口的基本繼承結構跟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都繼承于相應的read-only interface。

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

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沒有構建自己的集合類,仍然使用了Java中的集合類。Kotlin是通過只讀的集合接口來保證集合是只讀的,在底層實現上仍然是使用了Java中對應的可變集合。

  1. 集合的平臺類型(Collections as platform types):Java中的集合在Kotlin中有兩種表示:只讀的和可變的。再加上是否為可空類型,因此,在Kotlin中重寫或實現Java中含有集合類型的方法,你需要根據以下情況選擇合適Kotlin類型:
    • 集合是否為空。
    • 集合中的元素是否為空。
    • 是否需要修改集合。(read-only or mutable)

Operator overloading and other conventions

  1. 約定(conventions):在Java中實現了Iterable接口的類可以用在for循環中,Kotlin也有類似的語言特性,并且更加方便。與Java需要實現特定接口不同,Kotlin通過綁定特定名稱的函數來實現類似的特性,這種技術稱為約定,Kotlin中的運算符重載(operator overloading)就是使用這種技術。例如,如果一個類上定義了名為plus的函數,那么就可以按 約定 在這個類的對象上使用 + 運算符。
  2. rangeTo:前面說過任何實現了Comparable接口的類都可以用于創建range。這是因為Kotlin標準庫定義了
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

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

//LocalDate類是Java 8標準庫中的類,實現了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上,任意類只要實現了contains函數都可以使用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>)
}
委托代替屬性訪問被調用

Generics

  1. 原生類型(raw type):不存在的!Kotlin中的泛型必須指定類型參數(type argument),可以是顯式指定或者是編譯器推斷。如果想實現類似Java原生類型那樣的功能,可以使用星投影。
  2. 標記類型參數為非空的(making type parameters non-null):如果不對類型參數做限制,那么它可以是可空的類型。如果想保證類型參數非空,需要指定一個非空的上界,例如:
class Processor<T : Any> { 
    fun process(value: T) {
        value.hashCode()
    }
}
  1. 具體化(reified):reified可以具體化類型參數,奧秘在于inline而不是reified。因為函數定義為內聯的,編譯器將字節碼插入到每次函數調用的地方,生成的字節碼指向了具體的類型,而不是類型參數,所以編譯器知道類型參數的具體類型,不受類型擦除的影響。
  2. 具體化類型參數的限制(restrictions on reified type parameters)

可以:

  • 用于類型檢測和轉型(is , !is , as , as?)
  • 使用Kotlin反射(::class)
  • 獲取對應的 java.lang.Class (::class.java)
  • 作為類型參數調用別的函數

不可以:

  • 創建類型參數類的實例(new instance)
  • 調用類型參數類伴生對象的函數(Call methods on the companion object of the type parameter class)
  • 使用非具體化的類型參數調用具體化類型參數的函數(Use a non-reified type parameter as a type argument when calling a function with a reified type parameter)
  • 在類,屬性,非內聯函數上使用reified

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

  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中泛型的協變、逆變和不變。
  2. 星投影(star projection):當你不知道泛型類的類型參數是什么或者是什么并不重要時,可以使用星投影的語法。星投影對應于Java中的 無界通配符 ,即Kotlin中的MyType<*> 對應于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
    }
    
    //轉型不會有問題
    @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. 注解的目標(annotation targets):很多時候,Kotlin中單一的聲明對應于Java中多個聲明。例如,Kotlin中的property對應于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. 因此需要標明注解的是哪個元素,這稱為使用目標(use-site target)聲明,如下:
使用目標的語法

使用Java中定義的注解去注解Kotlin中的屬性,默認是應用在對應的field上的,可以使用下面的使用目標進行指定:

  • 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字節碼,以及怎樣被Java調用。一些Java中的keywords轉換成了Kotlin中的注解,例如@Volatile@Strictfp 就是Java中 volatilestrictfp 的代替。還有一些控制Kotlin中的聲明對Java調用者的可見性,例如:
    • @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反射的接口:所有的表達式都可以被注解,因此反射接口都繼承自KAnnotatedElementKClass代表class和object。KProperty代表任意屬性,它的子類KMutableProperty代表可變屬性(var)。KPropertyKMutableProperty內部還定義了GetterSetter 接口,當需要把 property accessors 當做函數時可以使用。圖中沒有顯示KProperty0接口,它可以用來代表頂層屬性(top-level property)。
Kotlin反射接口的繼承結構

與Java的不同

  1. 可變參數(vararg):與Java中使用三個點(...)不同,Kotlin使用vararg來表示 可變參數。另一個不同是,在Java中可以直接向 可變參數 傳遞一個數組,但是在Kotlin中,你必須顯式地把數組“展開”,這稱為伸展操作符(spread operator),實際就是在數組前加上 * 號,例如:
fun main(args: Array<String>) {
    val list = listOf("args: ", *args)
    println(list)
}
  1. 可見性:在Java中可以在同一個包內訪問protected成員,但是在Kotlin中不可以。Kotlin中,protected成員只對該類及其子類可見。另一點不同是,Kotlin中的外部類不能訪問 內部類/嵌套類 的private成員。但是在Java中可以。
  2. 對象表達式(object expression):對象表達式取代了Java中的匿名內部類,但是可以用來實現多個接口。還有一點與Java不同的是,在Kotlin的對象表達式的內部可以訪問創建它的函數的變量,不僅限于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. 內聯(inline):Java中的方法不支持inline,在Java中可以調用Kotlin中inline的函數,但是這些函數并不會內聯。如果Kotlin中定義的函數是inline并且reified,那么Java不能調用這樣的函數,Java不支持方法內聯,自然也就不能具體化參數類型。
  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. 命名參數(named argument):在Kotlin中調用Java的方法不能使用命名參數(包括JDK和Android Framework中的方法)。在 .class 文件中存儲參數名稱是從Java 8開始的一項可選特性。Kotlin兼容Java 6,因此編譯器不能識別命名參數,并把其匹配到方法的定義中。(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. 參數默認值(default parameter value):Java中沒有這個概念,因此從Java中調用Kotlin函數,必須使用所有參數。為方便起見,也可以在Kotlin中定義函數時加上 @JvmOverloads ,這樣編譯器會生成所有的 Java 重載方法。
  3. 頂層函數(top-level function):因為有頂層函數,所以Kotlin不需要靜態工具類(static utility class)。但是由于在JVM中,所有的代碼都必須在類中,因此,Kotlin中的頂層函數最終還是被編譯成了靜態工具類中的方法,類名是文件名(Utils.kt -> UtilsKt)。在Java中調用Kotlin的頂層函數,即是調用這些靜態工具類中的方法。想要修改這些靜態工具類的類名可以使用 @file:JvmName("NameYouWant") 注解(寫在文件的開始,package之前)。
  4. 擴展函數(extension function):在底層,擴展函數僅是把接收者(receiver object)作為第一個參數的靜態方法。在Java中可以像調用頂層函數那樣調用擴展函數,只是將接收者作為第一個參數傳入。
  5. 接口的默認實現:Kotlin接口中定義的函數可以包含有默認實現,但是Kotlin是兼容Java 6的,Java 6中不支持接口的默認實現的,因此,Kotlin中帶有默認實現的接口被編譯成了普通接口和包含有接口實現的類的組合。(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中還是需要實現Kotlin接口的所有方法,不論它在Kotlin中是否存在默認實現。Kotlin現在可以生成Java 8的字節碼,如果選擇Java 8作為target,則Kotlin接口的默認實現會編譯成像Java 8那樣。
  6. 可見性:Kotlin中的private類(只能對當前文件可見)被編譯成了包可見性,因為Java中不允許一個類是private的。而internal都被編譯成了public的(在字節碼層面)。這也就是為什么有的在Kotlin中不可見的類、函數在Java中反而可見。
  7. 空安全:Java不支持空安全,Java中的類型會轉變成Kotlin中的平臺類型(platform type),平臺類型本質上是沒有空信息的類型,既可以當做可空類型,也可以當做非空類型。
Java類型在Kotlin中被表示為平臺類型
  1. 函數類型(function types):在底層,函數類型都被聲明為了普通接口,像是Function0<R>(沒有參數),Function1<P1, R>(有一個參數)等等,每個接口中只包含一個 invoke 函數。在Java中調用Kotlin使用函數類型的函數(高階函數)很簡單,使用Java 8 的話,Java 8 中的lambda表達式自動轉換為了對應的函數類型;使用Java 8 之前的版本,需要傳入實現相應接口匿名內部類對象。
/* 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;
    }
});
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容