Kotlin和Java是有互操做性的(Interoperability). Kotlin和Java代碼能夠互相調用.html
爲何在一個項目裏這兩種語言會同時存在呢?java
由於Java中的全部引用都是可能爲null的. Java聲明的類型在Kotlin中被稱爲platform types
.安全
從Java中傳過來的引用, 能夠賦值給Kotlin的非空類型, 不會有編譯錯誤, 可是若是引用值爲空, 會在運行時拋出異常.bash
舉例, 在kotlin中調用Java的方法, 返回一個String:app
val stringOne = JavaUtils.getStringOne()
println(stringOne)
println(stringOne.length)
複製代碼
若是Java方法返回null怎麼辦? 運行這段程序時先打出null, 再拋出NullPointerException
.jvm
若是程序是這樣寫的:ide
val stringOne: String = JavaUtils.getStringOne()
println(stringOne)
println(stringOne.length)
複製代碼
說明Kotlin假設Java傳回來的是一個非空值. 這段代碼編譯時不會報錯, 可是運行時第一行就拋出IllegalStateException
.函數
若是這樣寫:測試
val stringOne: String? = JavaUtils.getStringOne()
println(stringOne)
println(stringOne?.length)
複製代碼
終於利用上了Kotlin的空安全檢查, 全部用到這個變量的地方都要加上?
, 若是不作檢查編譯時就會提示錯誤. 可是這樣防護不免致使代碼太囉嗦了, 可能處處都是?
.ui
好的實踐是Java中的Public APIs(Non-primitive parameters, Field type, Return)都應該加上註解.
若是Java的類型上有關於null的註解, 就會直接表示爲Kotlin中爲不爲null或者可爲null的對應類型.
註解能夠來自於各類包中, 好比JetBrains提供的: @Nullable
和@NotNull
.
好比:
@NotNull
public static String getStringOne() {
return "hello";
}
複製代碼
這樣Kotlin代碼就知道傳過來的確定是個非空值, 能夠放心使用.
若是是@Nullable
, 編譯器就會提示使用前作檢查.
Kotlin中的關鍵字, 好比:
fun, in, is, object, typealias, typeof, val, var, when
複製代碼
若是Java代碼中用了這些關鍵字, 在Kotlin中調用該Java代碼就要用`進行轉義.
好比若是java中有一個名稱爲is
的方法, 在kotlin中想要調用:
foo.`is`(bar)
複製代碼
可是, 首先須要考慮是否是名字起得很差, 若是能夠更名(不是第三方代碼), 優先考慮更名.
比較常見的一個使用情形是在寫測試的時候, Mockito中的when
就須要轉義:
Mockito.`when`(xxx.foo()).thenReturn(yyy)
複製代碼
由於Mockito是一個Java的第三方庫, 咱們無法改它.
另外一個解決辦法是使用import alias, 給這個方法取個別名:
import org.mockito.Mockito.`when` as whenever
複製代碼
這樣在使用的時候就能夠用whenever
來代替了when
了. import alias一般用來解決命名衝突的問題.
SAM: Single Abstract Method.
只要函數參數匹配, Kotlin的函數能夠自動轉換爲Java的接口實現.
Convention: 能夠作SAM轉換的參數類型應該放在方法的最後, 這樣看起來更舒服.
舉例, 若是在Java中定義方法:
interface Operation {
int doCalculate(int left, int right);
}
public static int calculate(Operation operation, int firstNumber, int secondNumber) {
return operation.doCalculate(firstNumber, secondNumber);
}
複製代碼
在Kotlin中調用的時候用SAM轉換, 用一個lambda做爲接口實現:
JavaUtils.calculate({ number1, number2 -> number1 + number2 }, 2, 3)
複製代碼
這樣雖然正確, 可是能夠改進. 把Java方法定義中的參數位置交換一下, 把接口參數放在最後:
public static int calculate(int firstNumber, int secondNumber, Operation operation) {
return operation.doCalculate(firstNumber, secondNumber);
}
複製代碼
在Kotlin中, 最後一個lambda參數能夠提取到括號外面:
JavaUtils.calculate(2, 3) { number1, number2 -> number1 + number2 }
複製代碼
這樣看起來更好.
注意: SAM conversion只應用於java interop.
上面的例子, 若是接口和方法是在Kotlin中定義的:
interface Operation2 {
fun doCalculate(left: Int, right: Int): Int
}
fun calculate2(firstNumber: Int, secondNumber: Int, operation: Operation2): Int {
return operation.doCalculate(firstNumber, secondNumber)
}
複製代碼
SAM conversions就不能用了, IDE會提示沒法識別. 調用這個方法時, 第三個參數必須寫成這種(匿名類的對象, 實現了接口):
calculate2(2, 3, object : Operation2 {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
複製代碼
這是由於在Kotlin的世界裏, 函數是第一公民.
若是把前面的方法參數改成function type:
fun calculate3(firstNumber: Int, secondNumber: Int, operation: (Int, Int) -> Int): Int {
return operation.invoke(firstNumber, secondNumber)
}
複製代碼
就能夠像以前SAM conversions似的使用:
calculate3(2, 3) { number1, number2 -> number1 + number2 }
複製代碼
若是接口是在Java中定義, 可是接收參數的方法是Kotlin的方法:
fun calculate4(firstNumber: Int, secondNumber: Int, operation: JavaUtils.Operation): Int {
return operation.doCalculate(firstNumber, secondNumber)
}
複製代碼
仍然是不能用SAM conversions, 由於這個方法仍然是能夠接受函數類型的參數的. 在Kotlin中調用:
calculate4(2, 3, object : JavaUtils.Operation {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
複製代碼
IDE會提示你簡化爲:
calculate4(2, 3, JavaUtils.Operation { left, right -> left + right })
複製代碼
注意這裏接口名稱不能省略.
是否是感受有點暈, 我把上面提到的幾個調用狀況寫在一塊兒:
// java function, java interface parameter
private fun trySAM1() {
JavaUtils.calculate(2, 3) { number1, number2 -> number1 + number2 }
}
// kotlin function, kotlin interface parameter
private fun trySAM2() {
calculate2(2, 3, object : Operation2 {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
}
// kotlin function, function type parameter
private fun trySAM3() {
calculate3(2, 3) { number1, number2 -> number1 + number2 }
}
// kotlin function, java interface parameter
private fun trySAM4() {
calculate4(2, 3, JavaUtils.Operation { left, right -> left + right })
}
複製代碼
能夠互相比較一下, 看看區別.
Java中的getter和setter在Kotlin中會表現爲properties. 可是若是隻有setter, 不會做爲可見的property.
Kotlin中全部的異常都是unchecked的, 因此若是調用的Java代碼有受檢異常, kotlin並不會強迫你處理.
Java中返回void的方法在Kotlin中會變成Unit
.
java.lang.Object
會變成Any
, Any
中的不少方法都是擴展方法.
Java沒有運算符重載, 可是Kotlin支持. (運算符重載容易存在過分使用的問題.)
Kotlin的屬性會被編譯成Java中的一個私有字段, 加上getter和setter方法.
若是想要做爲一個字段, 能夠加上@JvmField
註解.
若是在一個文件app.kt
中定義方法, 包名是org.example
, 會被編譯成Java的靜態方法, Java類的類名是org.example.AppKt
.
應用場景舉例: 舊代碼中有一個Java的輔助類, 包含靜態方法:
public class Utils {
public static int distanceBetween(int point1, int point2) {
return point2 - point1;
}
}
複製代碼
要把這個輔助類遷移到Kotlin代碼, 能夠新建一個Kotlin文件DistanceUtils.kt
, 直接寫包級別的方法:
fun distanceBetween(point1: Int, point2: Int): Int {
return point2 - point1
}
複製代碼
在Java中調用這個方法的時候:
DistanceUtilsKt.distanceBetween(7, 9);
複製代碼
若是原先的Java代碼中包含調用這個方法的地方太多, 又不想改全部的usage, 怎麼辦? -> 能夠經過註解@file:JvmmName("xxx")
改變類名. 這樣原先Java代碼中調用的地方就避免了修改.
若是有兩個文件指定了相同的JvmName, 編譯會報錯. 能夠經過加上@file:JvmMultifileClass
來解決. 這樣多個Kotlin文件中定義的輔助方法對於Java來講會統一到同一個類中.
如你須要把kotlin的property做爲字段暴露出來, 能夠加上@JvmField
註解.
適用的property: 有backing field, 沒有這些修飾符: private
, open
, override
, const
, 也不是代理屬性.
lateinit的屬性會自動暴露爲fields, 可見性和屬性的setter一致.
Kotlin的data class會自動生成getter/setter. 若是加上@JvmField
, 會直接暴露這些字段.
能夠經過@get:JvmName("xxx")
和@set:JvmName("xxx")
來定製getter和setter的名字.
Kotlin在有名字的object或者companion object中聲明的屬性, 將會編譯成靜態字段.
一般這些字段是private的, 不過也能夠經過如下幾種方式暴露:
@JvmField
.lateinit
.const
.前面提過, Kotlin包級別的方法會被編譯成靜態方法.
在object或companion object中聲明的方法, 默認是類中的實例方法. 好比:
class StaticMethodsDemoClass {
companion object {
fun sayHello() {
println("hello")
}
}
}
object SingletonObject {
fun sayWorld() {
println("world")
}
}
複製代碼
在Java中調用的時候:
StaticMethodsDemoClass.Companion.sayHello();
SingletonObject.INSTANCE.sayWorld();
複製代碼
若是給object或companion object中的方法加上@JvmStatic
, 會生成一個靜態方法和一個實例方法. 調用的時候就能夠省略掉中間的Companion
或INSTANCE
關鍵字, 以類名直接調用靜態方法.
好比:
class StaticMethodsDemoClass {
companion object {
fun sayHello() {
println("hello")
}
@JvmStatic
fun sayHelloStatic() {
println("hello")
}
}
}
複製代碼
調用的時候:
StaticMethodsDemoClass.Companion.sayHello();
//StaticMethodsDemoClass.sayHello(); // error
StaticMethodsDemoClass.Companion.sayHelloStatic(); // ok, but not necessary
StaticMethodsDemoClass.sayHelloStatic();
複製代碼
@JvmStatic
也能夠用於屬性, 就會有靜態版本的getter和setter方法.
Kotlin方法支持默認參數, 在Java中只有所有參數的方法簽名纔是可見的. 若是你但願對Java暴露多個方法重載, 要給方法加上@JvmOverloads
. 好比在Kotlin中寫一個自定義View的構造函數:
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}
複製代碼
還能夠利用 @JvmName
來給方法重命名. 由於在Kotlin中是擴展方法, 在Java中只是一個靜態方法, 名字可能不夠直觀.
Kotlin沒有checked exceptions. 若是想在Java中調用一個Kotlin方法, 幷包一個try-catch, 會報錯說沒有拋出這個異常.
能夠在Kotlin方法中加上註解, 好比@Throws(IOException::class)
.
Kotlin方法的參數名, 在生成代碼中會做爲一個字符串出現, 從而不會被混淆, 有可能會泄漏. 因此不建議放敏感信息到參數名中.
相似的還有字段名, 擴展方法名.
能夠在proguard中加上
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkParameterIsNotNull(...);
public static void throwUninitializedPropertyAccessException(...);
}
複製代碼
來移除這些代碼. 可是建議在測試環境中仍然保留這些代碼, 以便有錯誤發生的時候可以快速發現.
Code -> Convert Java File to Kotlin File
.
若是粘貼Java代碼到.kt文件, IDE會自動將所粘貼代碼轉換爲Kotlin代碼.
在IDE裏面能夠顯示Kotlin Bytecode, 而後decompile, 顯示java代碼.
Tools -> Kotlin -> Show Kotlin Bytecode -> Decompile
.