Kotlin 在設(shè)計時就考慮了 Java 互操作性。可以從 Kotlin 中自然地調(diào)用現(xiàn)存的 Java 代碼,并且在 Java 代碼中也可以
很順利地調(diào)用 Kotlin 代碼。在本節(jié)中我們會介紹從 Kotlin 中調(diào)用 Java 代碼的一些細(xì)節(jié)。
幾乎所有 Java 代碼都可以使用而沒有任何問題
import java.util.*
fun demo(source: List<Int>) {
val list = ArrayList<Int>()
// “for”-循環(huán)用于 Java 集合:
for (item in source) {
list.add(item)
}
// 操作符約定同樣有效:
for (i in 0..source.size() - 1) {
list[i] = source[i] // 調(diào)用 get 和 set
}
}
遵循 Java 約定的 getter 和 setter 的方法(名稱以 get 開頭的無參數(shù)方法和
以 set 開頭的單參數(shù)方法)在 Kotlin 中表示為屬性。 例如:
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // 調(diào)用 getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // 調(diào)用 setFirstDayOfWeek()
}
}
請注意,如果 Java 類只有一個 setter,它在 Kotlin 中不會作為屬性可見,因為 Kotlin 目前不支持只寫(set-only)屬性。
如果一個 Java 方法返回 void,那么從 Kotlin 調(diào)用時中返回 Unit。
萬一有人使用其返回值,它將由 Kotlin 編譯器在調(diào)用處賦值,
因為該值本身是預(yù)先知道的(是 Unit)。
一些 Kotlin 關(guān)鍵字在 Java 中是有效標(biāo)識符:in{: .keyword }、 object{: .keyword }、 is{: .keyword } 等等。
如果一個 Java 庫使用了 Kotlin 關(guān)鍵字作為方法,你仍然可以通過反引號(`)字符轉(zhuǎn)義它
來調(diào)用該方法
foo.`is`(bar)
Java 中的任何引用都可能是 null{: .keyword },這使得 Kotlin 對來自 Java 的對象要求嚴(yán)格空安全是不現(xiàn)實的。
Java 聲明的類型在 Kotlin 中會被特別對待并稱為平臺類型。對這種類型的空檢查會放寬,
因此它們的安全保證與在 Java 中相同(更多請參見下文)。
考慮以下示例:
val list = ArrayList<String>() // 非空(構(gòu)造函數(shù)結(jié)果)
list.add("Item")
val size = list.size() // 非空(原生 int)
val item = list[0] // 推斷為平臺類型(普通 Java 對象)
當(dāng)我們調(diào)用平臺類型變量的方法時,Kotlin 不會在編譯時報告可空性錯誤,
但在運(yùn)行時調(diào)用可能會失敗,因為空指針異常或者 Kotlin 生成的阻止空值傳播的斷言:
item.substring(1) // 允許,如果 item == null 可能會拋出異常
平臺類型是不可標(biāo)示的,意味著不能在語言中明確地寫下它們。
當(dāng)把一個平臺值賦值給一個 Kotlin 變量時,可以依賴類型推斷(該變量會具有推斷出的的平臺類型,
如上例中 item 所具有的類型),或者我們可以選擇我們期望的類型(可空或非空類型均可):
val nullable: String? = item // 允許,沒有問題
val notNull: String = item // 允許,運(yùn)行時可能失敗
如果我們選擇非空類型,編譯器會在賦值時觸發(fā)一個斷言。這防止 Kotlin 的非空變量保存
空值。當(dāng)我們把平臺值傳遞給期待非空值等的 Kotlin 函數(shù)時,也會觸發(fā)斷言。
總的來說,編譯器盡力阻止空值通過程序向遠(yuǎn)傳播(盡管鑒于泛型的原因,有時這
不可能完全消除)。
如上所述,平臺類型不能在程序中顯式表述,因此在語言中沒有相應(yīng)語法。
然而,編譯器和 IDE 有時需要(在錯誤信息中、參數(shù)信息中等)顯示他們,所以我們用
一個助記符來表示他們:
T! 表示“T 或者 T?”,(Mutable)Collection<T>! 表示“可以可變或不可變、可空或不可空的 T 的 Java 集合”,Array<(out) T>! 表示“可空或者不可空的 T(或 T 的子類型)的 Java 數(shù)組”具有可空性注解的Java類型并不表示為平臺類型,而是表示為實際可空或非空的
Kotlin 類型。編譯器支持多種可空性注解,包括:
org.jetbrains.annotations 包中的 @Nullable 和 @NotNull)com.android.annotations 和 android.support.annotations)javax.annotation)edu.umd.cs.findbugs.annotations)org.eclipse.jdt.annotation)lombok.NonNull)。你可以在 Kotlin 編譯器源代碼中找到完整的列表。
Kotlin 特殊處理一部分 Java 類型。這樣的類型不是“按原樣”從 Java 加載,而是 映射 到相應(yīng)的 Kotlin 類型。
映射只發(fā)生在編譯期間,運(yùn)行時表示保持不變。
Java 的原生類型映射到相應(yīng)的 Kotlin 類型(請記住平臺類型):
| Java 類型 | Kotlin 類型 |
|---|---|
byte |
kotlin.Byte |
short |
kotlin.Short |
int |
kotlin.Int |
long |
kotlin.Long |
char |
kotlin.Char |
float |
kotlin.Float |
double |
kotlin.Double |
boolean |
kotlin.Boolean |
{:.zebra}
一些非原生的內(nèi)置類型也會作映射:
| Java 類型 | Kotlin 類型 |
|---|---|
java.lang.Object |
kotlin.Any! |
java.lang.Cloneable |
kotlin.Cloneable! |
java.lang.Comparable |
kotlin.Comparable! |
java.lang.Enum |
kotlin.Enum! |
java.lang.Annotation |
kotlin.Annotation! |
java.lang.Deprecated |
kotlin.Deprecated! |
java.lang.CharSequence |
kotlin.CharSequence! |
java.lang.String |
kotlin.String! |
java.lang.Number |
kotlin.Number! |
java.lang.Throwable |
kotlin.Throwable! |
{:.zebra}
Java 的裝箱原始類型映射到可空的 Kotlin 類型:
| Java type | Kotlin type |
|---|---|
java.lang.Byte |
kotlin.Byte? |
java.lang.Short |
kotlin.Short? |
java.lang.Integer |
kotlin.Int? |
java.lang.Long |
kotlin.Long? |
java.lang.Char |
kotlin.Char? |
java.lang.Float |
kotlin.Float? |
java.lang.Double |
kotlin.Double? |
java.lang.Boolean |
kotlin.Boolean? |
{:.zebra}
請注意,用作類型參數(shù)的裝箱原始類型映射到平臺類型:
例如,List<java.lang.Integer> 在 Kotlin 中會成為 List<Int!>。
集合類型在 Kotlin 中可以是只讀的或可變的,因此 Java 集合類型作如下映射:
(下表中的所有 Kotlin 類型都駐留在 kotlin.collections包中):
| Java 類型 | Kotlin 只讀類型 | Kotlin 可變類型 | 加載的平臺類型 |
|---|---|---|---|
Iterator<T> |
Iterator<T> |
MutableIterator<T> |
(Mutable)Iterator<T>! |
Iterable<T> |
Iterable<T> |
MutableIterable<T> |
(Mutable)Iterable<T>! |
Collection<T> |
Collection<T> |
MutableCollection<T> |
(Mutable)Collection<T>! |
Set<T> |
Set<T> |
MutableSet<T> |
(Mutable)Set<T>! |
List<T> |
List<T> |
MutableList<T> |
(Mutable)List<T>! |
ListIterator<T> |
ListIterator<T> |
MutableListIterator<T> |
(Mutable)ListIterator<T>! |
Map<K, V> |
Map<K, V> |
MutableMap<K, V> |
(Mutable)Map<K, V>! |
Map.Entry<K, V> |
Map.Entry<K, V> |
MutableMap.MutableEntry<K,V> |
(Mutable)Map.(Mutable)Entry<K, V>! |
{:.zebra}
Java 的數(shù)組按下文所述映射:
| Java 類型 | Kotlin 類型 |
|---|---|
int[] |
kotlin.IntArray! |
String[] |
kotlin.Array<(out) String>! |
{:.zebra}
Kotlin 的泛型與 Java 有點(diǎn)不同(參見泛型)。當(dāng)將 Java 類型導(dǎo)入 Kotlin 時,我們會執(zhí)行一些轉(zhuǎn)換:
Java 的通配符轉(zhuǎn)換成類型投影
Foo<? extends Bar> 轉(zhuǎn)換成 Foo<out Bar!>!Foo<? super Bar> 轉(zhuǎn)換成 Foo<in Bar!>!Java的原始類型轉(zhuǎn)換成星投影
List 轉(zhuǎn)換成 List<*>!,即 List<out Any?>!和 Java 一樣,Kotlin 在運(yùn)行時不保留泛型,即對象不攜帶傳遞到他們構(gòu)造器中的那些類型參數(shù)的實際類型。
即 ArrayList<Integer>() 和 ArrayList<Character>() 是不能區(qū)分的。
這使得執(zhí)行 is{: .keyword }-檢測不可能照顧到泛型。
Kotlin 只允許 is{: .keyword }-檢測星投影的泛型類型:
if (a is List<Int>) // 錯誤:無法檢查它是否真的是一個 Int 列表
// but
if (a is List<*>) // OK:不保證列表的內(nèi)容
與 Java 不同,Kotlin 中的數(shù)組是不型變的。這意味著 Kotlin 不允許我們把一個 Array<String> 賦值給一個 Array<Any>,
從而避免了可能的運(yùn)行時故障。Kotlin 也禁止我們把一個子類的數(shù)組當(dāng)做超類的數(shù)組傳遞給 Kotlin 的方法,
但是對于 Java 方法,這是允許的(通過 Array<(out) String>! 這種形式的平臺類型)。
Java 平臺上,數(shù)組會使用原生數(shù)據(jù)類型以避免裝箱/拆箱操作的開銷。
由于 Kotlin 隱藏了這些實現(xiàn)細(xì)節(jié),因此需要一個變通方法來與 Java 代碼進(jìn)行交互。
對于每種原生類型的數(shù)組都有一個特化的類(IntArray、 DoubleArray、 CharArray 等等)來處理這種情況。
它們與 Array 類無關(guān),并且會編譯成 Java 原生類型數(shù)組以獲得最佳性能。
假設(shè)有一個接受 int 數(shù)組索引的 Java 方法:
public class JavaArrayExample {
public void removeIndices(int[] indices) {
// 在此編碼……
}
}
在 Kotlin 中你可以這樣傳遞一個原生類型的數(shù)組:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array) // 將 int[] 傳給方法
當(dāng)編譯為 JVM 字節(jié)代碼時,編譯器會優(yōu)化對數(shù)組的訪問,這樣就不會引入任何開銷:
val array = arrayOf(1, 2, 3, 4)
array[x] = array[x] * 2 // 不會實際生成對 get() 和 set() 的調(diào)用
for (x in array) { // 不會創(chuàng)建迭代器
print(x)
}
即使當(dāng)我們使用索引定位時,也不會引入任何開銷
for (i in array.indices) {// 不會創(chuàng)建迭代器
array[i] += 2
}
最后,in{: .keyword }-檢測也沒有額外開銷
if (i in array.indices) { // 同 (i >= 0 && i < array.size)
print(array[i])
}
Java 類有時聲明一個具有可變數(shù)量參數(shù)(varargs)的方法來使用索引。
public class JavaArrayExample {
public void removeIndices(int... indices) {
// 在此編碼……
}
}
在這種情況下,你需要使用展開運(yùn)算符 * 來傳遞 IntArray:
val javaObj = JavaArray()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
目前無法傳遞 null{: .keyword } 給一個聲明為可變參數(shù)的方法。
由于 Java 無法標(biāo)記用于運(yùn)算符語法的方法,Kotlin 允許
具有正確名稱和簽名的任何 Java 方法作為運(yùn)算符重載和其他約定(invoke() 等)使用。
不允許使用中綴調(diào)用語法調(diào)用 Java 方法。
在 Kotlin 中,所有異常都是非受檢的,這意味著編譯器不會強(qiáng)迫你捕獲其中的任何一個。
因此,當(dāng)你調(diào)用一個聲明受檢異常的 Java 方法時,Kotlin 不會強(qiáng)迫你做任何事情:
fun render(list: List<*>, to: Appendable) {
for (item in list) {
to.append(item.toString()) // Java 會要求我們在這里捕獲 IOException
}
}
當(dāng) Java 類型導(dǎo)入到 Kotlin 中時,類型 java.lang.Object 的所有引用都成了 Any。
而因為 Any 不是平臺指定的,它只聲明了 toString()、hashCode() 和 equals() 作為其成員,
所以為了能用到 java.lang.Object 的其他成員,Kotlin 要用到擴(kuò)展函數(shù)。
Effective Java 第 69 條善意地建議優(yōu)先使用并發(fā)工具(concurrency utilities)而不是 wait() 和 notify()。
因此,類型 Any 的引用不提供這兩個方法。
如果你真的需要調(diào)用它們的話,你可以將其轉(zhuǎn)換為 java.lang.Object:
(foo as java.lang.Object).wait()
要取得對象的 Java 類,請在類引用上使用 java 擴(kuò)展屬性。
val fooClass = foo::class.java
上面的代碼使用了自 Kotlin 1.1 起支持的綁定的類引用。你也可以使用 javaClass 擴(kuò)展屬性。
val fooClass = foo.javaClass
要覆蓋 clone(),需要繼承 kotlin.Cloneable:
class Example : Cloneable {
override fun clone(): Any { …… }
}
不要忘記 Effective Java 的第 11 條: 謹(jǐn)慎地改寫clone。
要覆蓋 finalize(),所有你需要做的就是簡單地聲明它,而不需要 override{:.keyword} 關(guān)鍵字:
class C {
protected fun finalize() {
// 終止化邏輯
}
}
根據(jù) Java 的規(guī)則,finalize() 不能是 private{: .keyword } 的。
在 kotlin 中,類的超類中最多只能有一個 Java 類(以及按你所需的多個 Java 接口)。
Java 類的靜態(tài)成員會形成該類的“伴生對象”。我們無法將這樣的“伴生對象”作為值來傳遞,
但可以顯式訪問其成員,例如:
if (Character.isLetter(a)) {
// ……
}
Java 反射適用于 Kotlin 類,反之亦然。如上所述,你可以使用 instance::class.java,ClassName::class.java 或者 instance.javaClass 通過 java.lang.Class 來進(jìn)入 Java 反射。
其他支持的情況包括為一個 Kotlin 屬性獲取一個 Java 的 getter/setter 方法或者幕后字段、為一個 Java 字段獲取一個 KProperty、為一個 KFunction 獲取一個 Java 方法或者構(gòu)造函數(shù),反之亦然。
就像 Java 8 一樣,Kotlin 支持 SAM 轉(zhuǎn)換。這意味著 Kotlin 函數(shù)字面值可以被自動的轉(zhuǎn)換成
只有一個非默認(rèn)方法的 Java 接口的實現(xiàn),只要這個方法的參數(shù)類型
能夠與這個 Kotlin 函數(shù)的參數(shù)類型相匹配。
你可以這樣創(chuàng)建 SAM 接口的實例:
val runnable = Runnable { println("This runs in a runnable") }
……以及在方法調(diào)用中:
val executor = ThreadPoolExecutor()
// Java 簽名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }
如果 Java 類有多個接受函數(shù)式接口的方法,那么可以通過使用
將 lambda 表達(dá)式轉(zhuǎn)換為特定的 SAM 類型的適配器函數(shù)來選擇需要調(diào)用的方法。這些適配器函數(shù)也會按需
由編譯器生成。
executor.execute(Runnable { println("This runs in a thread pool") })
請注意,SAM 轉(zhuǎn)換只適用于接口,而不適用于抽象類,即使這些抽象類也只有一個
抽象方法。
還要注意,此功能只適用于 Java 互操作;因為 Kotlin 具有合適的函數(shù)類型,所以不需要將函數(shù)自動轉(zhuǎn)換
為 Kotlin 接口的實現(xiàn),因此不受支持。
要聲明一個在本地(C 或 C++)代碼中實現(xiàn)的函數(shù),你需要使用 external 修飾符來標(biāo)記它:
external fun foo(x: Int): Double
其余的過程與 Java 中的工作方式完全相同。