1. 程式人生 > 程式設計 >在Java與Kotlin之間如何進行互操作詳解

在Java與Kotlin之間如何進行互操作詳解

前言

目前kotlin是谷歌首推的開發Android的語言,但由於歷史原因,我們絕大部分專案依舊還是以Java為主的,也就是說存在Java和Kotlin兩種語言同時開發的情況。

有人會說把老專案全部翻譯成Kotlin,的確可以怎麼做,但是成本還是挺大的。我們只能一點一點慢慢的向kotlin語言遷移。

那麼在遷移的過程中就避免不了Java和Kotlin相互呼叫的情況。即Kotlin呼叫Java或者Java呼叫Kotlin。下面我們就來具體看下兩者之間相互操作的一些解決方案。

kotlin呼叫java

可空性(Nullability)

Java預設有數值可空性而kotlin沒有,所以在呼叫Java的方法的時候不知道會不會收到空值。

所以我們在Kotlin中呼叫Java的時候需要新增 ?或者 !來告訴Kotlin有可能出現空值。

比如這裡有一個Java方法,接受一組字串後返回一組做字串:

public Set<String> toSet(Collection<String> elements){
 //TODO
}

那麼Kotlin在呼叫的時候是不能確定輸入和輸出是否可為空的。就需要使用?或者 !來​輔助判斷。​

為了方便Kotlin呼叫,我們通常使用 @NotNull 註解來標識Java程式碼的非原始引數、欄位、返回值。

@NotNull
Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements){
 //TODO
}

這個Kotlin在呼叫的時候就明確知道不能為空,這裡我們使用的是jetBrain的 @NotNull註解,當然還有其他選擇,如下圖:

這裡還是推薦使用JetBrain或者Android的註解。

字首屬性:(getter、setter)

如果是使用Java bean,那麼我們在Kotlin中呼叫就沒有什麼問題。

如果你的空引數方法是以get開頭的,那麼Kotlin就知道這是getter,就可以通過屬性名來訪問它。

相同的如果是由set開頭的單一引數方法,那麼Kotlin就知道這是setter,就通過屬性名直接賦值。

當然is的工作原理也是和它們類似的。

我們定義一個Java bean:

class User {
 
 private String name;
 private int age;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public int getAge() {
  return age;
 }

 public void setAge(int age) {
  this.age = age;
 }
}

Kotlin中訪問

val user = User()
user.name = "四爺" //賦值
val age = user.age //獲取age欄位值

關鍵字(keywords)

kotlin中有很多系統定義的關鍵字如 fun is in objects、typeof、val、var、when、typealias等。

這些關鍵字在java是可以被使用的,但是在kotlin卻是不行的。

函式或者引數使用了這些關鍵字,那麼kotlin在呼叫的時候會出現一些問題,比如Java中定義了一個方法名叫 is 的方法。那麼在Kotlin中直接呼叫就會報錯。

那麼最簡單的方法就是重新命名Java方法,但如果呼叫的是三方庫的方法,就很難去重新命名了。
所以我們另一種解決方式是在Kotlin呼叫java方法的時候加上 `` 反引號來使用。

Utils.`is`()

但是我們如果能重新命名還是重新命名,以防止程式碼出現太多的符號。

避免在任何擴充套件方法和擴充套件屬性上使用Any

運算子過載(operator Overloading)

在Java不存在運算子過載,而kotlin有。比如:

a+b => a.plus(b)

在kotlin中將運算子 + 翻譯為了方法 plus。

如果在Java中使用了同樣的方法名稱,比如 加(plus)、 減(minus)或者其他運算子名稱,那麼請務必確保他們與運算子相容,避免意外呼叫他們。

Java呼叫Kotlin

JvmName & JvmMultifileClass

當我們在遷移的時候會將Java的工具類翻譯為Kotlin拓展函式或者頂層函式。但是這樣處理之後,在Java檔案中是無法直接呼叫的,此時我們需要加註解 @file:JvmName(“檔名稱”):

Ext.kt檔案

@file:JvmName("ExtUtils")
package com.demo.javaAndKotlin

fun a(): String {
 ...
}

fun b(): String {
 ...
}

這裡我們將名稱命名為ExtUtils。此外,我們可能還有其他的頂層函式或者擴充套件函式。按照上面這種方式我們也可以指定一個其他的名稱,但是如果我們也想使用ExtUtils這個名稱的時候會報錯:

Duplicate JVM class name

此時我們需要在不同的檔案中加入新的註解 @file:JvmMultifileClass 。意思是將所有的檔案合併到一個新的名稱為ExtUtils檔案中。

ExtOther.kt檔案

@file:JvmMultifileClass
@file:JvmName("ExtUtils")
package com.demo.javaAndKotlin

fun c(str: Any): String {
 ...
}

我們在Ext.kt檔案中也加入@file:JvmMultifileClass註解,我們就可以在Java檔案中直接使用ExtUtils來呼叫 a(),b(),c()方法了。

JvmField

在 kotlin中我們使用的資料類即 data class 是不需要指定getter和setter的,可以直接通過欄位名來訪問它們。但是如果是在Java檔案中呼叫data class依舊是需要使用getter和setter方法進行呼叫的。這裡我們是可以修改他們的,那就是使用 @JvmField 註解,通過註解,可以直接將欄位暴露出去進行訪問。​

data class Person(

 @JvmField var name: String,@JvmField var age: Int
)

//java中呼叫
Person person = new Person("",1);
person.name = "";
person.age = 10;

但是也有例外就是lateinit修飾的欄位會自動暴露,無需指定@JvmField註解。還有const修飾的欄位也是一樣會自動暴露。

另外,如果我們想在Java中呼叫setName的時候修改這個屬性名稱不叫setName,這裡我們需要使用@set:JvmName 註解。同理修改getName使用@get:JvmName 。需要注意的是,指定了@set:JvmName或者@get:JvmName註解後不需要在指定@JvmField了。

data class Person(

 @set:JvmName("changeName")
 var name: String,@JvmField var age: Int,@get:JvmName("likesPink")
 var likesPink: Boolean
){
 lateinit var address:String
}

JvmStatic

當我們將Java檔案的靜態方法遷移到Kotlin中時,我們會將其放在 companion object中,但是這樣處理之後在Java檔案中無法直接呼叫,得通過companion物件例項方法來呼叫。

class MyService {
 internal fun doWork() {
  ...
 }

 companion object {
  fun schedule(context: Context) {
   ...
  }
 }
}

//在Java中呼叫
MyService.Companion.schedule(this);

幸運的是Kotlin提供了 @JvmStatic 註解。他會讓Kotlin在編譯器完成類封裝後生成一個靜態方法。

class MyService {
 internal fun doWork() {
  ...
 }

 companion object {
   @JvmStatic
  fun schedule(context: Context) {
   ...
  }
 }
}

//在Java中呼叫
MyService.schedule(this);

JvmOverloads

在Kotlin中我們可以給函式的引數設定預設值,即預設引數。但是這個功能在Java中是沒有的。如果不做任何處理,那麼在Java中呼叫函式的時候,就必須每個引數都要傳入。那麼我們設定的預設引數就沒有任何意義了。

所以,Kotlin給我們提供了 @JvmOverloads註解,使用這個註解後,會讓Kotlin編譯器按照從左向右的順序依次為每一個可選引數生成過載。

@JvmOverloads
fun Bitmap.resize(width: Int,height: Int = 200) {

}

//java呼叫
ExtUtils.resize(bitmap,100);

這裡我們在Kotlin中很容易就理解了Bitmap.resize方法的含義,但是ExtUtils.resize這樣呼叫的時候,方法名不夠明確。所以我們可以使用@JvmName註解來指定名稱。

@JvmName("resizeBitmap")
@JvmOverloads
fun Bitmap.resize(width: Int,height: Int = 200) {

}
//java呼叫
ExtUtils.resizeBitmap(bitmap,100);

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支援。