Kotlin的互操作——Kotlin與Java互相呼叫
互操作就是在Kotlin中可以呼叫其他程式語言的介面,只要它們開放了介面,Kotlin就可以呼叫其成員屬性和成員方法,這是其他程式語言所無法比擬的。同時,在進行Java程式設計時也可以呼叫Kotlin中的API介面。
Kotlin與Java互操作
1 Kotlin呼叫Java
Kotlin在設計時就考慮了與Java的互操作性。可以從Kotlin中自然地呼叫現有的Java程式碼,在Java程式碼中也可以很順利地呼叫Kotlin程式碼。
【例1】在Kotlin中呼叫Java的Util的list庫。
packagejqiang.Mutual.Kotlin
importjava.util.*
fundemo(source:List<Int>){
vallist=ArrayList<Int>()
for(iteminlist){
list.add(item)
}
for(iin0..source.size-1){
list[i]=source[i]
}
}
基本的互操作行為如下:
1.屬性讀寫
Kotlin可以自動識別Java中的getter/setter;在Java中可以過getter/setter操作Kotlin屬性。
【例2】自動識別Java中的getter/setter。
packagejqiang.Mutual.Kotlin
importjava.util.*
funmain(args:Array<String>){
valcalendar=Calendar.getInstance()
println(calendar.firstDayOfWeek)
if(calendar.firstDayOfWeek==1){// 呼叫getFirstDayOfWeek()方法
calendar.firstDayOfWeek=2// 呼叫setFirstDayOfWeek()方法
}
println(calendar.firstDayOfWeek)
}
遵循Java約定的getter和setter方法(名稱以get開頭的無引數方法和以set開頭的單引數方法)在Kotlin中表示為屬性。如果Java類只有一個setter,那麼它在Kotlin中不會作為屬性可見,因為Kotlin目前不支援只寫(set-only)屬性。
2.空安全型別
Kotlin的空安全型別的原理是,Kotlin在編譯過程中會增加一個函式呼叫,對引數型別或者返回型別進行控制,開發者可以在開發時通過註解@Nullable和@NotNull方式來彌補Java中空值異常。
Java中的任何引用都可能是null,這使得Kotlin對來自Java的物件進行嚴格的空安全檢查是不現實的。Java宣告的型別在Kotlin中稱為平臺型別,並會被特別對待。對這種型別的空檢查要求會放寬,因此對它們的安全保證與在Java中相同。
【例3】空值例項。
vallist=ArrayList<String>()//非空(建構函式結果)
list.add("Item")
val size=list.size()//非空(原生Int)
Val item=list[0]//推斷為平臺型別(普通Java物件)
當呼叫平臺型別變數的方法時,Kotlin不會在編譯時報告可空性錯誤,但是在執行時呼叫可能會失敗,因為空指標異常。
item.substring(1)//允許,如果item==null可能會丟擲異常
平臺型別是不可標識的,這意味著不能在程式碼中明確地寫下它們。當把一個平臺值賦給一個Kotlin變數時,可以依賴型別推斷(該變數會具有所推斷出的平臺型別,如上例中item所具有的型別),或者選擇我們所期望的型別(可空的或非空型別均可)。
val nullable:String?=item//允許,沒有問題
Val notNull:String=item//允許,執行時可能失敗
如果選擇非空型別,編譯器會在賦值時觸發一個斷言,這樣可以防止Kotlin的非空變數儲存空值。當把平臺值傳遞給期待非空值等的Kotlin函式時,也會觸發一個斷言。總的來說,編譯器盡力阻止空值通過程式向遠傳播(由於泛型的原因,有時這不可能完全消除)。
3.返回void的方法
如果在Java中返回void,那麼Kotlin返回的就是Unit。如果在呼叫時返回void,那麼Kotlin會事先識別該返回值為void。
4.註解的使用
@JvmField是Kotlin和Java互相操作屬性經常遇到的註解;@JvmStatic是將物件方法編譯成Java靜態方法;@JvmOverloads主要是Kotlin定義預設引數生成過載方法;@file:JvmName指定Kotlin檔案編譯之後生成的類名。
5.NoArg和AllOpen
資料類本身屬性沒有預設的無引數的構造方法,因此Kotlin提供一個NoArg外掛,支援JPA註解,如@Entity。AllOpen是為所標註的類去掉final,目的是為了使該類允許被繼承,且支援Spring註解,如@Componet;支援自定義註解型別,如@Poko。
6.泛型
Kotlin中的萬用字元“”代替Java中的“?”;協變和逆變由Java中的extends和super變成了out和in,如ArrayList;在Kotlin中沒有Raw型別,如Java中的List對應於Kotlin就是List<>。
與Java一樣,Kotlin在執行時不保留泛型,也就是物件不攜帶傳遞到它們的構造器中的型別引數的實際型別,即ArrayList()和ArrayList()是不能區分的。這使得執行is檢查不可能照顧到泛型,Kotlin只允許is檢查星投影的泛型型別。
if(aisList<Int>)//錯誤:無法檢查它是否真的是一個Int列表
if(aisList<*>)//OK:不保證列表的內容
7.SAM轉換
就像Java 8一樣,Kotlin支援SAM轉換,這意味著Kotlin函式字面值可以被自動轉換成只有一個非預設方法的Java介面的實現,只要這個方法的引數型別能夠與這個Kotlin函式的引數型別相匹配就行。
【例4】首先使用Java建立一個SAMInJava類,然後通過Kotlin呼叫Java中的介面。
Java程式碼:
packagejqiang.Mutual.Java;
import java.util.ArrayList;
public class SAMInJava{
private ArrayList<Runnable>runnables=newArrayList<Runnable>();
public void addTask(Runnablerunnable){
runnables.add(runnable);
System.out.println("add:"+runnable+",size"+runnables.size());
}
Public void removeTask(Runnablerunnable){
runnables.remove(runnable);
System.out.println("remove:"+runnable+"size"+runnables.size());
}
}
Kotlin程式碼:
packagejqiang.Mutual.Kotlin
importjqiang.Mutual.Java.SAMInJava
funmain(args:Array<String>){
varsamJava=SAMInJava()
vallamba={
print("hello")
}
samJava.addTask(lamba)
samJava.removeTask(lamba)
}
執行結果如下:
add:jqiang.Mutual.Kotlin.SamKt$sam$Runnable$bef91c64@63947c6bsize1
remove:jqiang.Mutual.Kotlin.SamKt$sam$Runnable$bef91c64@2b193f2dsize1
如果Java類有多個接受函式式介面的方法,那麼可以通過使用將Lambda表示式轉換為特定的SAM型別的介面卡函式來選擇需要呼叫的方法。這些介面卡函式也會按需由編譯器生成。
vallamba={
print("hello")
}
samJava.addTask(lamba)
SAM轉換隻適用於介面,而不適用於抽象類,即使這些抽象類只有一個抽象方法。此功能只適用於Java互操作;因為Kotlin具有合適的函式型別,所以不需要將函式自動轉換為Kotlin介面的實現,因此不受支援。
2 Java呼叫Kotlin
在Java中可以輕鬆地呼叫Kotlin程式碼。
1.屬性
Kotlin屬性會被編譯成以下Java元素:
- getter方法,其名稱通過加字首get得到;
- setter方法,其名稱通過加字首set得到(只適用於var屬性);
- 私有欄位,與屬性名稱相同(僅適用於具有幕後欄位的屬性)。
【例5】將Kotlin變數編譯成Java中的變數宣告。
Kotlin部分程式碼:
varfirstName:String
Java部分程式碼:
privateStringfirstName;
publicStringgetFirstName(){
returnfirstName;
}
publicvoidsetFirstName(StringfirstName){
this.firstName=firstName;
}
如果屬性名稱是以is開頭的,則使用不同的名稱對映規則:getter的名稱與屬性名稱相同,並且setter的名稱是通過將is替換成set獲得的。例如,對於屬性isOpen,其getter會稱作isOpen(),而其setter會稱作setOpen()。這一規則適用於任何型別的屬性,並不僅限於Boolean。
2.包級函式
在jqiang.Mutual.Kotlin包內的example.kt檔案中宣告的所有函式和屬性,包括擴充套件函式,都被編譯成一個名為jqiang.Mutual.Kotlin.ExampleKt的Java類的靜態方法。
【例6】包級函式呼叫。
Kotlin部分程式碼:
packagejqiang.Mutual.Kotlin
funbar(){
println("這只是一個bar方法")
}
Java部分程式碼:
packagejqiang.Mutual.Java;
publicclassexample{
publicstaticvoidmain(String[]args){
jqiang.Mutual.Kotlin.ExampleKt.bar();
}
}
可以使用@JvmName註解修改所生成的Java類的類名:
@file:JvmName("example")
packagejqiang.Mutual.Kotlin
那麼Java呼叫時就需要修改類名:
jqiang.Mutual.Kotlin.example.bar();
在多個檔案中生成相同的Java類名(包名相同並且類名相同或者有相同的@JvmName註解)通常是錯誤的。然而,編譯器能夠生成一個單一的Java外觀類,它具有指定的名稱且包含來自於所有檔案中具有該名稱的所有宣告。要生成這樣的外觀,請在所有的相關檔案中使用@JvmMultifileClass註解。
@file:JvmName("example")
@file:JvmMultifileClass
packagejqiang.Mutual.Kotlin
3.例項欄位
如果需要在Java中將Kotlin屬性作為欄位暴露,那麼就需要使用@JvmField註解對其進行標註。該欄位將具有與底層屬性相同的可見性。如果一個屬性有幕後欄位(Backing Field)、非私有的、沒有open/override或者const修飾符,並且不是被委託的屬性,那麼可以使用@JvmField註解該屬性。
4.靜態方法
Kotlin將包級函式表示為靜態方法。如果對這些函式使用@JvmStatic進行標註,那麼Kotlin還可以為在命名物件或伴生物件中定義的函式生成靜態方法。如果使用該註解,那麼編譯器既會在相應物件的類中生成靜態方法,也會在物件自身中生成例項方法。例如:
classC{
companionobject{
@JvmStaticfunfoo(){}
funbar(){}
}
}
現在,foo()在Java中是靜態的,而bar()不是靜態的。
C.foo();//沒問題
C.bar();//錯誤:不是一個靜態方法
C.Companion.foo();//保留例項方法
C.Companion.bar();//唯一的工作方式
對於命名物件也同樣:
objectObj{
@JvmStaticfunfoo(){}
funbar(){}
}
在Java中:
Obj.foo();//沒問題
Obj.bar();//錯誤
Obj.INSTANCE.bar();//沒問題,通過單例例項呼叫
Obj.INSTANCE.foo();// 也沒問題
@JvmStatic註解也可以被應用於物件或伴生物件的屬性上,使其getter和setter方法在該物件或包含該伴生物件的類中是靜態成員。
5.可見性
Kotlin的可見性以下列方式對映到Java。
(1)private成員被編譯成private成員。
(2)private的頂層宣告被編譯成包級區域性宣告。
(3)protected依然保持protected(注意,Java允許訪問同一個包中其他類的受保護成員,而Kotlin則不允許,所以Java類會訪問更廣泛的程式碼)。
(4)internal宣告會成為Java中的public。internal類的成員會通過名字修飾,使其更難以在Java中被意外使用到,並且根據Kotlin規則使其允許過載相同簽名的成員而互不可見。
(5)public依然保持public。
6.空安全性
當從Java中呼叫Kotlin函式時,沒有任何方法可以阻止Kotlin中的空值傳入。Kotlin在JVM虛擬機器中執行時會檢查所有的公共函式,可以檢查非空值,這時候就可以通過NullPointerException得到Java中的非空值程式碼。
7.型變的泛型
當Kotlin使用了宣告處型變時,可以通過兩種方式從Java程式碼中看到它們的用法。假設有以下類和兩個使用它的函式:
class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
將這兩個函式轉換成Java程式碼:
Box<Derived> boxDerived(Derived value) { … }
Base unboxBase(Box<Base> box) { … }
在Kotlin中可以這樣寫:unboxBase(boxDerived(“s”)),但是在Java中是行不通的,因為在Java中Box類在其泛型引數T上是不型變的,於是Box並不是Box的子類。要使其在Java中工作,需要按以下方式定義unboxBase:
Base unboxBase(Box<? extends Base> box) { … }
這裡使用Java的萬用字元型別(? extends Base)通過使用處型變來模擬宣告處型變,因為在Java中只能這樣。
當它作為引數出現時,為了讓Kotlin的API在Java中工作,對於協變定義的Box生成Box作為Box
相關文章
- [Kotlin基礎] Java 呼叫 Kotlin(一)KotlinJava
- java呼叫kotlin注意事項JavaKotlin
- Kotlin 1.6.20釋出:更好的Java互操作性 - infoworldKotlinJava
- Kotlin-48.JavaScript呼叫Kotlin(Call Kotlin from JavaScript)KotlinJavaScript
- Kotlin 與 Java 對比KotlinJava
- Kotlin與java的糾纏史KotlinJava
- Kotlin基礎:望文生義的Kotlin集合操作Kotlin
- Kotlin 與 JAVA 不同之處KotlinJava
- 從 Java 到 Kotlin - 介紹 KotlinJavaKotlin
- Kotlin 知識梳理(2) 函式的定義與呼叫Kotlin函式
- Java與Kotlin的單例模式(霸氣.jpg)JavaKotlin單例模式
- groovy與javah互相呼叫Java
- kotlin 的Elvis 操作符Kotlin
- FROM JAVA BUILDERS TO KOTLIN DSLSJavaUIKotlin
- 從Java到Kotlin(八)JavaKotlin
- 從Java到Kotlin(三)JavaKotlin
- 從Java到Kotlin(四)JavaKotlin
- 從Java到Kotlin(二)JavaKotlin
- 從Java到Kotlin(一)JavaKotlin
- 從Java到Kotlin(六)JavaKotlin
- 從Java到Kotlin(五)JavaKotlin
- 從Java到Kotlin(七)JavaKotlin
- 【Kotlin】初識Kotlin(二)Kotlin
- Dive Into Kotlin(一):初探 KotlinKotlin
- 【Kotlin】Kotlin環境搭建Kotlin
- CPS 與 Kotlin coroutineKotlin
- Kotlin 使用Rxjava的compose()操作符KotlinRxJava
- Kotlin——初級篇(五):操作符與操作符過載一Kotlin
- 用Java optional模仿Kotlin? - WelshJavaKotlin
- Kotlin & Java 之單利模式KotlinJava模式
- Kotlin和Java的簡單對比KotlinJava
- Kotlin實戰【五】Kotlin中的異常Kotlin
- Kotlin 知識梳理(6) Kotlin 的可空性Kotlin
- Js呼叫Java方法並互相傳參JSJava
- 【思貨】kotlin協程優雅的與Retrofit纏綿-kotlin DSL簡述Kotlin
- Kotlin 操作符:run、with、let、also、apply 的差異與選擇KotlinAPP
- Kotlin---集合與遍歷Kotlin
- XTask與Kotlin Coroutine的使用對比Kotlin