Java 與 Kotlin 的交互 及 差異

liy Elaine
liy Elaine
Published in
11 min readNov 7, 2020

這篇文章 , 會介紹 kotlin 和 java 之間 相互調用 或者是其它 特性,會需要特別注意 Kotlin 以及 和 Java 的差異之處。同樣的程式碼 在 Java 和 kotlin 之間有何不同。

空值 Nullity

Java 和 kotlin Nullity 差別

在 Java 中 不論是 變數 或是 function 返回值 ,是可以直接傳遞 空值的,Java 在 編譯階段 並不會檢查空值, 直到運行(runtime)時 爆炸 為止,這部分對開發者來說,是個大患,kotlin 改善了這項缺點,在編譯時就會先進行空安全檢查。kotlin 也增加了幾個 方式去避免空值的錯誤的發生。

在 Java 的型態是可以空值的

String message = null

在kotlin中只有 型態 加上?才可接受空值

val message : String? = null

當 kotlin 呼叫 Java

如上 Java 並不會特別分別出 接受的是空值或是非空,在 kotlin 呼叫 java 的變數或返回值 , 並無法分辨出是非空值 還是 空值 , 這使 kotlin 的空安全檢查失效 , 此時 在 kotlin 對應的型態 會加上 一個驚嘆號 例如 String!, 表示在kotlin 接受的值有可能是 空值 也可能是非空值 。

在 Java 的 getValue 返回 null


public class Data {
public String getValue() {
return null;
}
}

下面 的 value 的型態 , 會是 String !, kotlin 並無法檢查出空值 , 在 Java getValue() 回傳空值,因此一不小心 ,就在運行時發生錯誤。

fun main(args: Array<String>) {
val data = Data()
val value = data.getValue()
println(value.toLowerCase())
}

可以怎麼做?

@Nullable annotion

當使用此 annotion ,代表該方法有可能會 返回 null 值 , 讓 Kotlin 編譯器可以識別。
這樣子,kotlin 空安全檢查 機制 才能起到作用。

@Nullable
public class Data {
public String getValue() {
return null;
}
}

加上 annotion 之後 , Kotlin 可以辨識到 有可能會是空值, 並要求做處理,如下 value?

fun main(args: Array<String>) {
val data = Data()
val value = data.getValue()
println(value?.toLowerCase())
}

型態 Type mapping

kotlin類型 會映射 Java 的類型 , 例如調用 Int 類型操作時 , 實際上在 runtime 運作的會是 Java 基本型別 int 。

Java 中 的類別 大部分都會是 object , 除了基本型別不是 , 而在 Kotlin 中的 object 包括基本型別 ,
這也是為什麼 Java 的 基本型別 不能調用函數處理 , 而 kotlin 可以的原因 。

kotlin 映射 Java 的類型 , 提供 object ,但 保有 java 原始 型別的特性。

調用 函數

下面kotlin程式碼中 count類型 int , 這裡的 count 是 Java 基本型別 不能調用 函數

public class Data {int count = 100 
public Int getCount() {
return count;
}
}

下面 kotlin 程式碼中 count 的類型為 Int 映射 Java 類型 int , 這裡的 count 可調用函數。

fun main(args: Array<String>) {
val data = Data()
val count = data.getCount()
// 可調用函數
println(count .dec())
// 映射 的 java
println(count .javaclass)
}

class 類別

Java 呼叫 kotlin file (非 class 之內)的 function 或變數

kotlin file 除了 class 可以是 function 或是 變數(static), 在 Java 一個 file 就是一個 class ,所以 Java 呼叫 這些 function 或變數時 , 需要在file 名字 加上Kt 再去調用。

如下:

file : Communication.kt

fun sendMessage() = "Hello"

file 名字 Communication加上Kt 調用sendMessage function

public class Jhava {
...
public static void main(String[] args) {
System.out.println(CommunicationKt.sendMessage());
}
...
}

method overloading

Kotlin 具有 named function arguments , 可以在一個 function 執行 overloading, 與 Java 相較之下,簡潔許多。

在 kotlin 調用下面 function 時可以選填 leftHand 或是 rightHand , 也可以不填 , 當不填時,會使用 default 值,

fun handOverFood(leftHand: String = "berries", rightHand: String = "beef") {
println("Mmmm... you hand over some delicious $leftHand and $rightHand.")
}

形同 下面三個 Java function 的組成。

public static void handOverFood(String leftHand, String rightHand) {
System.out.println("Mmmm... you hand over some delicious " +
leftHand + " and " + rightHand + ".");
}
public static void handOverFood(String leftHand) {
handOverFood(leftHand, "beef");
}
public static void handOverFood() {
handOverFood("berries", "beef");
}

但 在 java 調用同一個 funtion 時 執行 選填時 , 會無法執行,因為 Java沒有 默認方法參數 ,也無法區分

下面會 compiler error

public class Jhava {
...

public void offerFood() {
Hero.handOverFood("pizza");
}
}

Java 取得 或 設定 Kotlin class 頂層變數

Java 和 Kotlin 取得 或 設定 Kotlin class 頂級變數的方式 不同, Java 需要透過 get 或 set ,而 Kotlin 可直接調用。

class Spellbook {
val spells = listOf("Magic Ms. L", "Lay on Hans")
}
val spellbook = Spellbook()
val spells = spellbook.spells
Spellbook spellbook = new Spellbook();
List<String> spells = spellbook.getSpells();

取得 或 設定 Kotlin Company Object

在 調用時 Kotlin Company Object 內的函數或變數時, Kotlin 可以直接透過 Company Object 所在的 class 調用(class.funtion()),而 Java 需要先調用 Company Object 才有辦法調用裡面的函數或變數 (class.Companion.funtion());

class Spellbook {

companion object {
val MAX_SPELL_COUNT = 10
}
}

在 Kotlin 調用

val value = Spellbook. MAX_SPELL_COUNT

在 Java 調用

int value = Spellbook. Companion.MAX_SPELL_COUNT

Exceptions

unchecked exceptions (RuntimeException, Error, and their subclasses)

是由編程問題引起的問題,也就是自己邊寫的程式碼有誤,包括 trying to access an object through a null reference , indexing exceptions, attempting to access an array element through an index that is too large or too small 等等 ,這種問題可能會在程序中的任何位置發生,並不明確異常會在何處發生,因此難以捕捉。所以編譯器 並不會 強制捕捉錯誤。

checked exceptions

Checked Exception 是一些 會預料到的Error ,例如網路連線失敗會 產生 IO Exception, 對這此種錯誤我們可以事先做處理 ,防止在運行期間出現錯誤,這部分 compiler 會檢查有無對 Checked Exception 進行處理 。

兩者的差異

兩者的差異在於,checked exceptions 可以合理地預期客戶端將從異常中恢復。unchecked exceptions client無法措施 將異常恢復 。

Kotlin 和 Java 差異

Java 具有 unchecked exceptions 和 checked exceptions,而 kotlin 皆是 unchecked exceptions 並無 checked exceptions,由於Kotlin 在編譯期間就會進行檢查,例如空安全檢查,所以並不需要大量的 checked exceptions 影響可讀性。
由於 在 Kotlin 皆是 unchecked exceptions, 在 Java 如果以補捉的方式檢查,編譯器 警告 這Exeption 不會發生。

fun acceptApology() {
throw IOException()
}
// 編譯器會警告這個 IOException 是 不會發生的public void apologize() {
try {
Hero.acceptApology();
} catch (IOException e) {
System.out.println("Caught!");
}
}

解決方式是在 Java 的 function 加上 @Throws annotation ,這樣一來Java 就有辦法對 Kotlin 的 Exeption( unchecked exceptions) 做處理

@Throws(IOException::class)
fun acceptApology() {
throw IOException()
}
class Spellbook {

}

Function Types (Lambdas 表達式)

在 kotlin 中,function 也可以 當作是 type , 也就是 Lambdas 表達式

class People {    val greeting:(String) -> Unit = { it ->println(it)}}

如果轉成 bytecode , 會發現 在 Java 會是 一個 泛型 interface
in 代表是 傳進來參數 的 型態 , out 代表 回傳的型態,


import kotlin.jvm.functions.*

public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}

限制能夠傳入的參數最多 22 個

import kotlin.jvm.functions.* 

public interface Function22<in P1, in P2, in P3,....,in P22, out R> : Function<R> {
public operator fun invoke(p1: P1....,p22:P22): R
}

如下 lambdas 表達是 在 Java 型態 的結構會是 Function+傳入參數數量+<in,out>

class People {    val greeting:(String) -> Unit = { it ->println(it)}}

上面 greeting 有一個傳入參數 , 傳入型態為 String , 沒有回傳值所以是 Unit , 在 Java 中型態會是Function1<String,Unit>


public class Data {
...
public void send (){
People people = new People();
Function1<String,Unit> message = people .getGreeting();
}}

在Java 中會是這樣調用lambdas 表達式 ,

// 加上這行
message.invoke("hello");

--

--