Kotlin與Java互操作__Java

來源:互聯網
上載者:User

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


Kotlin調用Java


Kotlin在設計時就考慮了與Java的互通性。可以從Kotlin中自然地調用現有的Java代碼,在Java代碼中也可以很順利地調用Kotlin代碼。例如,在Kotlin中調用Java的Util的list庫。



基本的互操作行為如下:


屬性讀寫


Kotlin可以自動識別Java中的getter/setter函數,而在Java中可以過getter/setter操作Kotlin屬性。



循Java約定的getter和setter方法(名稱以get開頭的無參數方法和以set開頭的單參數方法)在Kotlin中表示為屬性。如果Java類只有一個setter,那麼它在Kotlin中不會作為屬性可見,因為Kotlin目前不支援唯寫(set-only)屬性。


空安全類型


Kotlin的空安全類型的原理是,Kotlin在編譯過程中會增加一個函數調用,對參數類型或者傳回型別進行控制,開發人員可以在開發時通過註解@Nullable和@NotNull方式來限制Java中空值異常。


Java中的任何引用都可能是null,這使得Kotlin對來自Java的對象進行嚴格的空安全檢查是不現實的。Java聲明的類型在Kotlin中稱為平台類型,並會被特別對待。對這種類型的空檢查要求會放寬,因此對它們的安全保證與在Java中相同。



當調用平台類型變數的方法時,Kotlin不會在編譯時間報告可空性錯誤,但是在運行時調用可能會失敗,因為空白指標異常。



平台類型是不可標識的,這意味著不能在代碼中明確地標識它們。當把一個平台值賦給一個Kotlin變數時,可以依賴類型推斷(該變數會具有所推斷出的平台類型,如上例中item所具有的類型),或者選擇我們所期望的類型(可空的或非空類型均可)。



如果選擇非空類型,編譯器會在賦值時觸發一個斷言,這樣可以防止Kotlin的非空變數儲存空值。當把平台值傳遞給期待非空值等的Kotlin函數時,也會觸發一個斷言。總的來說,編譯器儘力阻止空值的傳播(由於泛型的原因,有時這不可能完全消除)。


平台類型標識法


如上所述,平台類型不能在程式中顯式表述,因此在語言中沒有相應文法。 然而,編譯器和 IDE 有時需要(在錯誤資訊中、參數資訊中等)顯示他們,Koltin提供助記符來表示他們:

T! 表示“T 或者 T?”;


(Mutable)Collection! 表示“可以可變或不可變、可空或不可空的 T 的 Java 集合”;


Array<(out) T>! 表示“可空或者不可空的 T(或 T 的子類型)的 Java 數組”。


可空註解


由於泛型的原因,Kotlin在編譯時間可能出現空異常,而使用空註解可以有效解決這一情況。編譯器支援多種可空性註解:


JetBrains:org.jetbrains.annotations 包中的 

@Nullable 和 @NotNull;


Android:com.android.annotations 和 android.support.annotations;


JSR-305:javax.annotation;


FindBugs:edu.umd.cs.findbugs.annotations;


Eclipse:org.eclipse.jdt.annotation;


Lombok:lombok.NonNull;


JSR-305 支援


在JSR-305中,定義的 @Nonnull 註解來表示 Java 類型的可空性。


如果 @Nonnull(when = ...) 值為 When.ALWAYS,那麼該註解類型會被視為非空;When.MAYBE 與 When.NEVER 表示可空類型;而 When.UNKNOWN 強制類型為平台類型。


可針對 JSR-305 註解編譯庫,但不需要為庫的消費者將註解構件(如 jsr305.jar)指定為編譯依賴。Kotlin 編譯器可以從庫中讀取 JSR-305 註解,並不需要該註解出現在類路徑中。


自 Kotlin 1.1.50 起, 也支援自訂可空限定符(KEEP-79)


類型限定符


如果一個註解類型同時標註有 @TypeQualifierNickname 與 JSR-305 @Nonnull(或者它的其他別稱,如 @CheckForNull),那麼該註解類型自身將用於 檢索精確的可空性,且具有與該可空性註解相同的含義。



類型限定符預設值


@TypeQualifierDefault 引入應用時在所標註元素的範圍內定義預設可空性的註解。這些註解類型應自身同時標註有 @Nonnull(或其別稱)與 @TypeQualifierDefault(...) 註解, 後者帶有一到多個 ElementType 值。


ElementType.METHOD 用於方法的傳回值;


ElementType.PARAMETER 用於值參數;


ElementType.FIELD 用於欄位;


ElementType.TYPE_USE(自 1.1.60 起)適用於任何類型,包括型別參數、型別參數的上界與萬用字元類型。


當類型並未標註可空性註解時使用預設可空性,並且該預設值是由最內層標註有帶有與所用類型相匹配的 ElementType 的類型限定符預設註解的元素確定。



也支援包級的預設可空性:



@UnderMigration 註解


庫的維護者可以使用 @UnderMigration 註解(在單獨的構件 kotlin-annotations-jvm 中提供)來定義可為空白性類型限定符的遷移狀態。


@UnderMigration(status = ...) 中的狀態值指定了編譯器如何處理 Kotlin 中註解類型的不當用法(例如,使用 @MyNullable 標註的類型值作為非空值):


MigrationStatus.STRICT 使註解像任何純可空性註解一樣工作,即對不當用法報錯並影響註解聲明內的類型在 Kotlin中的呈現;


對於 MigrationStatus.WARN,不當用法報為警告而不是錯誤; 但註解聲明內的類型仍是平台類型;


MigrationStatus.IGNORE 則使編譯器完全忽略可空性註解。


庫的維護者還可以將 @UnderMigration 狀態添加到類型限定符別稱與類型限定符預設值中。例如:



注意:可空性註解的遷移狀態並不會從其類型限定符別稱繼承,而是適用於預設類型限定符的用法。如果預設類型限定符使用類型限定符別稱,並且它們都標註有 @UnderMigration,那麼使用預設類型限定符的狀態。


返回void的方法


如果在Java中返回void,那麼Kotlin返回的就是Unit。如果在調用時返回void,那麼Kotlin會事先識別該傳回值為void。


註解的使用


@JvmField是Kotlin和Java互相操作屬性經常遇到的註解;@JvmStatic是將對象方法編譯成Java靜態方法;@JvmOverloads主要是Kotlin定義預設參數產生重載方法;@file:JvmName指定Kotlin檔案編譯之後產生的類名。


NoArg和AllOpen


資料類本身屬性沒有預設的無參數的構造方法,因此Kotlin提供一個NoArg外掛程式,支援JPA註解,如@Entity。AllOpen是為所標註的類去掉final,目的是為了使該類允許被繼承,且支援Spring註解,如@Componet;支援自訂註解類型,如@Poko。


泛型


Kotlin 的泛型與 Java 有點不同,讀者可以具體參考泛型章節。Kotlin中的萬用字元“”代替Java中的“。”;協變和逆變由Java中的extends和super變成了out和in,如ArrayList;在Kotlin中沒有Raw類型,如Java中的List對應於Kotlin就是List<>。


與Java一樣,Kotlin在運行時不保留泛型,也就是對象不攜帶傳遞到它們的構造器中的型別參數的實際類型,即ArrayList()和ArrayList()是不能區分的。這使得執行is檢查不可能照顧到泛型,Kotlin只允許is檢查星投影的泛型型別。



Java數組


與 Java 不同,Kotlin 中的數組是不型變的。這意味著 Kotlin 不允許我們把一個 Array 賦值給一個 Array, 從而避免了可能的運行時故障。Kotlin 也禁止我們把一個子類的數組當做超類的數組傳遞給 Kotlin 的方法, 但是對於 Java 方法,這是允許的(通過 Array<(out) String>! 這種形式的平台類型)。


Java 平台上,數組會使用原生資料類型以避免裝箱/拆箱操作的開銷。 由於 Kotlin 隱藏了這些實現細節,因此需要一個變通方法來與 Java 代碼進行互動。 對於每種原生類型的數組都有一個特化的類(IntArray、 DoubleArray、 CharArray 等等)來處理這種情況。 它們與 Array 類無關,並且會編譯成 Java 原生類型數組以獲得最佳效能。


例如,假設有一個接受 int 數組索引的 Java 方法。



在 Kotlin 中調用該方法時,你可以這樣傳遞一個原生類型的數組。



當編譯為 JVM 位元組代碼時,編譯器會最佳化對數組的訪問,這樣就不會引入任何開銷。



即使當我們使用索引定位時,也不會引入任何開銷:



最後,in-檢測也沒有額外開銷:



Java 可變參數


Java 類有時聲明一個具有可變數量參數(varargs)的方法來使用索引。



在這種情況下,你需要使用展開運算子 * 來傳遞 IntArray。



目前,無法傳遞 null 給一個聲明為可變參數的方法。


SAM轉換


就像Java 8一樣,Kotlin支援SAM轉換,這意味著Kotlin函數字面值可以被自動轉換成只有一個非預設方法的Java介面的實現,只要這個方法的參數類型能夠與這個Kotlin函數的參數類型相匹配就行。


首先使用Java建立一個SAMInJava類,然後通過Kotlin調用Java中的介面。



然後在Kotlin中調用該Java介面。



運行結果為:



如果Java類有多個接受函數式介面的方法,那麼可以通過使用將Lambda運算式轉換為特定的SAM類型的適配器函數來選擇需要調用的方法。



注意:SAM轉換隻適用於介面,而不適用於抽象類別,即使這些抽象類別只有一個抽象方法。此功能只適用於Java互操作;因為Kotlin具有合適的函數類型,所以不需要將函數自動轉換為Kotlin介面的實現,因此不受支援。


除此之外,Kotlin調用Java還有很多的內容,讀者可以通過下面的連結來瞭解:Kotlin調用Java


Java調用Kotlin


Java 可以輕鬆調用 Kotlin 代碼。


屬性


Kotlin屬性會被編譯成以下Java元素:


getter方法,其名稱通過加首碼get得到;


setter方法,其名稱通過加首碼set得到(只適用於var屬性);


私人欄位,與屬性名稱相同(僅適用於具有幕後欄位的屬性)。


例如,將Kotlin變數編譯成Java中的變數聲明。



如果屬性名稱是以is開頭的,則使用不同的名稱映射規則:getter的名稱與屬性名稱相同,並且setter的名稱是通過將is替換成set獲得的。例如,對於屬性isOpen,其getter會稱作isOpen(),而其setter會稱作setOpen()。這一規則適用於任何類型的屬性,並不僅限於Boolean。


包級函數


例如,在org.foo.bar 包內的 example.kt 檔案中聲明的所有的函數和屬性,包括擴充函數, 該 類會編譯成一個名為 org.foo.bar.ExampleKt 的 Java 類的靜態方法。


首先,建立一個ExampleKt.kt的檔案,並建立一個bar函數:



然後,在Java中調用這個函數。



當然,可以使用@JvmName註解修改所產生的Java類的類名。例如:



那麼在Java調用時就需要修改類名。例如:



在多個檔案中產生相同的Java類名(包名相同並且類名相同或者有相同的@JvmName註解)通常是錯誤的。然而,編譯器能夠產生一個單一的Java外觀類,它具有指定的名稱且包含來自於所有檔案中具有該名稱的所有聲明。要產生這樣的外觀,請在所有的相關檔案中使用@JvmMultifileClass註解。



執行個體欄位


如果需要在Java中將Kotlin屬性作為欄位暴露,那麼就需要使用@JvmField註解對其進行標註。使用@JvmField註解標註後,該欄位將具有與底層屬性相同的可見度。如果一個屬性有幕後欄位(Backing Field)、非私人的、沒有open/override或者const修飾符,並且不是被委託的屬性,那麼可以使用@JvmField註解該屬性。


首先,建立一個kt類,並添加如下代碼。



然後在Java中調用該代碼,



延遲初始化的屬性(在Java中)也會暴露為欄位, 該欄位的可見度與 lateinit 屬性的 setter 相同。


靜態欄位


在命名物件或伴生對象時,聲明的 Kotlin 屬性會在該命名物件或包含伴生對象的類中包含靜態幕後欄位。通常這些欄位是私人的,但可以通過以下方式之一暴露出來。


@JvmField 註解;


lateinit 修飾符;


const 修飾符。


使用 @JvmField 標註的屬性,可以使其成為與屬性本身具有相同可見度的靜態欄位。例如:



然後,在Java代碼中調用屬性。



在命名物件或者伴生對象中的一個延遲初始化的屬性具有與屬性 setter 相同可見度的靜態幕後欄位。



然後,在Java中使用該欄位的屬性。



用 const 標註的(在類中以及在頂層的)屬性在 Java 中會成為靜態欄位,首先建立一個kt檔案。



然後,在Java中可以直接調用該屬性即可。



靜態方法


Kotlin將包級函數表示為靜態方法。如果對這些函數使用@JvmStatic進行標註,那麼Kotlin還可以為在命名物件或伴生對象中定義的函數產生靜態方法。如果使用該註解,那麼編譯器既會在相應對象的類中產生靜態方法,也會在對象自身中產生執行個體方法。例如:



現在,foo()在Java中是靜態,而bar()不是靜態。



對於命名物件,也存在同樣的規律。



在 Java 中使用。



@JvmStatic 註解也可以應用於對象或伴生對象的屬性, 使其 getter 和 setter 方法在該對象或包含該伴生對象的類中是靜態成員。


可見度


Kotlin的可見度以下列方式映射到Java代碼中。


private 成員編譯成 private 成員;


private 的頂層聲明編譯成包級局部聲明;


protected 保持 protected(注意 Java 允許訪問同一個包中其他類的受保護的成員, 而 Kotlin 不能,所以Java 類會訪問更廣泛的代碼);


internal 聲明會成為 Java 中的 public。internal 類的成員會通過名字修飾,使其更難以在 Java 中意外使用到,並且根據 Kotlin 規則使其允許重載相同簽名的成員而互不可見;


public 保持 public。


KClass


有時你需要調用有 KClass 型別參數的 Kotlin 方法。 因為沒有從 Class 到 KClass 的自動轉換,所以你必須通過調用 Class.kotlin 擴充屬性的等價形式來手動進行轉換。例如:



簽名衝突


有時我們想讓一個 Kotlin 中的命名函數在位元組碼中有另外一個 JVM 名稱,最突出的例子是由於類型擦除引發的。



這兩個函數不能同時定義在一個類中,因為它們的 JVM 簽名是一樣的。如果我們真的希望它們在 Kotlin 中使用相同的名稱,可以使用 @JvmName 去標註其中的一個(或兩個),並指定不同的名稱作為參數。例如:



在 Kotlin 中它們可以用相同的名稱 filterValid 來訪問,而在 Java 中,它們分別是 filterValid 和 filterValidInt。同樣的技巧也適用於屬性中。例如:



產生重載


通常,如果你寫一個有預設參數值的 Kotlin 函數,在 Java 中只會有一個所有參數都存在的完整參數簽名的方法可見,如果希望向 Java 調用者暴露多個重載,可以使用 @JvmOverloads 註解。該註解可以用於建構函式、靜態方法中,但不能用於抽象方法和在介面中定義的方法。



對於每一個有預設值的參數,都會產生一個額外的重載,這個重載會把這個參數和它右邊的所有參數都移除掉。在上例中,會產生以下代碼 。



請注意,如次建構函式中所述,如果一個類的所有建構函式參數都有預設值,那麼會為其產生一個公有的無參建構函式,此時就算沒有 @JvmOverloads 註解也有效。


受檢異常


如上所述,Kotlin 沒有受檢異常。 所以,通常 Kotlin 函數的 Java 簽名不會聲明拋出異常, 於是如果我們有一個這樣的 Kotlin 函數。首先,建立一個kt檔案。



然後,在 Java 中調用它的時候,需要使用try{}catch{}來捕捉這個異常。



因為 foo() 沒有聲明 IOException,我們從 Java 編譯器得到了一個報錯訊息。 為瞭解決這個問題,要在 Kotlin 中使用 @Throws 註解。



空安全性


當從Java中調用Kotlin函數時,沒有任何方法可以阻止Kotlin中的空值傳入。Kotlin在JVM虛擬機器中運行時會檢查所有的公用函數,可以檢查非空值,這時候就可以通過NullPointerException得到Java中的非空值代碼。


型變的泛型


當 Kotlin 的類使用了聲明處型變時,可以通過兩種方式從Java代碼中看到它們的用法。讓我們假設我們有以下類和兩個使用它的函數:



將這兩個函數轉換成Java代碼如下:



問題是,在 Kotlin 中我們可以這樣寫 unboxBase(boxDerived("s")),但是在 Java 中是行不通的,因為在 Java 中類 Box 在其泛型參數 T 上是不型變的,於是 Box 並不是 Box 的子類。 要使其在 Java 中工作,我們按以下這樣定義 unboxBase。



這裡我們使用 Java 的萬用字元類型(? extends Base)來通過使用處型變來類比聲明處型變,因為在 Java 中只能這樣。


當它作為參數出現時,為了讓 Kotlin 的 API 在 Java 中工作,對於協變定義的 Box 我們產生 Box 作為 Box<? extends Super> (或者對於逆變定義的 Foo 產生 Foo<? super Bar>)。當它是一個傳回值時, 我們不產生萬用字元,因為否則 Java 用戶端將必須處理它們(並且它違反常用 Java 編碼風格)。因此,我們的樣本中的對應函數實際上翻譯如下:



注意:當參數類型是 final 時,產生萬用字元通常沒有意義,所以無論在什麼地方 Box 始終轉換為 Box。如果我們在預設不產生萬用字元的地方需要萬用字元,我們可以使用 @JvmWildcard 註解:



另一方面,如果我們根本不需要預設的萬用字元轉換,我們可以使用@JvmSuppressWildcards。



注意:@JvmSuppressWildcards 不僅可用於單個型別參數,還可用於整

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.