kotlin和java混合開發,Kotlin的互操作——Kotlin與Java互相調用

 2023-12-09 阅读 30 评论 0

摘要:互操作就是在Kotlin中可以調用其他編程語言的接口,只要它們開放了接口,Kotlin就可以調用其成員屬性和成員方法,這是其他編程語言所無法比擬的。同時,在進行Java編程時也可以調用Kotlin中的API接口。 Kotlin與Java互操作 1 Kotlin調用Java kotlin

互操作就是在Kotlin中可以調用其他編程語言的接口,只要它們開放了接口,Kotlin就可以調用其成員屬性和成員方法,這是其他編程語言所無法比擬的。同時,在進行Java編程時也可以調用Kotlin中的API接口。

Kotlin與Java互操作

1 Kotlin調用Java

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]}
}

java類與類之間的調用?基本的互操作行為如下:

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

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/5/193864.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息