Kotlin极简教程:第10章 Kotlin与Java互操作
Kotlin is 100% interoperable with Java? and Android?
在前面的章節(jié)中,我們已經(jīng)學(xué)習(xí)了Kotlin的基礎(chǔ)語(yǔ)法、類型系統(tǒng)、泛型與集合類、面向?qū)ο笈c函數(shù)式編程等主題,在上一章中我們還看到了Kotlin提供的輕量級(jí)并發(fā)編程模型:協(xié)程的相關(guān)內(nèi)容。
從本章開(kāi)始到在后面的章節(jié)中,我們將進(jìn)入工程代碼的實(shí)戰(zhàn)。我們將在后面分別介紹Kotlin集成SpringBoot開(kāi)發(fā)服務(wù)端Web項(xiàng)目、使用Kotlin開(kāi)發(fā)Android項(xiàng)目,以及使用Kotlin來(lái)寫(xiě)前端JavaScript代碼的等主題。
Kotlin 的競(jìng)爭(zhēng)優(yōu)勢(shì)在于它并不是完全隔離于 Java 語(yǔ)言。它基本上是可與 Java 100% 互操作的。這樣,Kotlin就可以站在整個(gè)Java生態(tài)巨人的肩上,向著更遠(yuǎn)大的前程前進(jìn)。
本章我們就讓我們一起來(lái)學(xué)習(xí)下Kotlin與Java的互操作。
Kotlin 調(diào)用 Java示例
Kotlin 很像 Java。它長(zhǎng)得不像 Clojure 或者 Scala 那么奇怪(承認(rèn)現(xiàn)實(shí)把,這兩種語(yǔ)言就是挺奇怪的)。所以我們學(xué) Kotlin 應(yīng)該很快。這門(mén)語(yǔ)言顯然就是寫(xiě)給 Java 開(kāi)發(fā)者來(lái)用的。
Kotlin 在設(shè)計(jì)之初就考慮了與 Java 的互操作性。我們可以從 Kotlin 中自然地調(diào)用現(xiàn)存的 Java 代碼。例如,下面是一個(gè)Kotlin調(diào)用Java中的Okhttp庫(kù)的代碼:
package com.easy.kotlinimport okhttp3.* import java.io.File import java.io.IOException import java.util.concurrent.TimeUnitobject OkhttpUtils {fun get(url: String): String? {var result: String? = ""val okhttp = OkHttpClient.Builder().connectTimeout(1, TimeUnit.HOURS).readTimeout(1, TimeUnit.HOURS).writeTimeout(1, TimeUnit.HOURS).build()val request = Request.Builder().url(url).build()val call = okhttp.newCall(request)try {val response = call.execute()result = response.body()?.string()val f = File("run.log")f.appendText(result!!)f.appendText("\n")} catch (e: IOException) {e.printStackTrace()}return result} }Kotlin調(diào)用Java代碼跟Groovy一樣流暢自如(但是不像Groovy那樣“怎么寫(xiě)都對(duì),但是一運(yùn)行就報(bào)錯(cuò)”,因?yàn)镚roovy是一門(mén)動(dòng)態(tài)類型語(yǔ)言,而Kotlin則是一門(mén)強(qiáng)類型的靜態(tài)類型語(yǔ)言)。我們基本不需要改變什么就可以直接使用Java中的API庫(kù)。
并且在 Java 代碼中也可以很順利地調(diào)用 Kotlin 代碼:
package com.easy.kotlin;import com.alibaba.fastjson.JSON;public class JSONUtils {public static String toJsonString(Object o) {return JSON.toJSONString(o);}public static void main(String[] args) {String url = "http://www.baidu.com";String result = OkhttpUtils.INSTANCE.get(url);System.out.println(result);} }因?yàn)镵otlin跟Java本是兩門(mén)語(yǔ)言,所以在互相調(diào)用的時(shí)候,會(huì)有一些特殊的語(yǔ)法。這里的使用Java調(diào)用Kotlin的object對(duì)象函數(shù)的語(yǔ)法就是OkhttpUtils.INSTANCE.get(url), 我們看到這里多了個(gè)INSTANCE 。
我們甚至也可以在一個(gè)項(xiàng)目中同時(shí)使用Kotlin和Java兩 種語(yǔ)言混合編程。我們可以在下一章中看到,我們?cè)谝粋€(gè)SpringBoot工程中同時(shí)使用了Kotlin和Java兩種語(yǔ)言進(jìn)行混合開(kāi)發(fā)。
下面我們來(lái)繼續(xù)介紹 Kotlin 調(diào)用 Java 代碼的一些細(xì)節(jié)。
Kotlin使用Java的集合類
Kotlin的集合類API很多就是直接使用的Java的API來(lái)實(shí)現(xiàn)的。我們?cè)谑褂玫臅r(shí)候,毫無(wú)違和感,自然天成:
@RunWith(JUnit4::class) class KotlinUsingJavaTest {@Test fun testArrayList() {val source = listOf<Int>(1, 2, 3, 4, 5)// 使用Java的ArrayListval list = ArrayList<Int>()for (item in source) {list.add(item) // ArrayList.add()}for (i in 0..source.size - 1) {list[i] = source[i] // 調(diào)用 get 和 set}} }Kotlin調(diào)用Java中的Getter 和 Setter
在Java中遵循這樣的約定: getter 方法無(wú)參數(shù)并以 get 開(kāi)頭,setter 方法單參數(shù)并以 set 開(kāi)頭。在 Kotlin 中我們可以直接表示為屬性。 例如,我們寫(xiě)一個(gè)帶setter和getter的Java類:
package com.easy.kotlin;import java.util.Date;public class Product {Long id;String name;String category;Date gmtCreated;Date gmtModified;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getCategory() {return category;}public void setCategory(String category) {this.category = category;}public Date getGmtCreated() {return gmtCreated;}public void setGmtCreated(Date gmtCreated) {this.gmtCreated = gmtCreated;}public Date getGmtModified() {return gmtModified;}public void setGmtModified(Date gmtModified) {this.gmtModified = gmtModified;} }然后,我們?cè)贙otlin可以直接使用屬性名字進(jìn)行g(shù)et和set操作:
@RunWith(JUnit4::class) class ProductTest {@Test fun testGetterSetter() {val product = Product()product.name = "賬務(wù)系統(tǒng)"product.category = "金融財(cái)務(wù)類"product.gmtCreated = Date()product.gmtModified = Date()println(JSONUtils.toJsonString(product))Assert.assertTrue(product.getName() == "賬務(wù)系統(tǒng)")Assert.assertTrue(product.name == "賬務(wù)系統(tǒng)")Assert.assertTrue(product.getCategory() == "金融財(cái)務(wù)類")Assert.assertTrue(product.category == "金融財(cái)務(wù)類")} }當(dāng)然,我們還可以像在Java中一樣,直接調(diào)用像product.getName()、product.setName(“Kotlin”)這樣的getter、setter方法。
調(diào)用Java中返回 void 的方法
如果一個(gè) Java 方法返回 void,那么從 Kotlin 調(diào)用時(shí)中返回 Unit。
public class Admin {String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Admin{" +"name='" + name + '\'' +'}';} }我們這樣調(diào)用
val setReturn = admin.setName("root") println(setReturn)將輸出:kotlin.Unit
空安全和平臺(tái)類型
我們知道Java 中的任何引用都可能是null,這樣我們?cè)谑褂?Kotlin調(diào)用來(lái)自 Java 的對(duì)象的時(shí)候就有可能會(huì)出現(xiàn)空安全的問(wèn)題。
Java 聲明的類型在 Kotlin 中會(huì)被特別對(duì)待并稱為平臺(tái)類型(platform types )。對(duì)這種類型的空檢查會(huì)放寬,因此它們的安全保證與在 Java 中相同。
請(qǐng)看以下示例:
@RunWith(JUnit4::class) class CallingJavaNullSafe {@Test fun testCallingJavaNullSafe() {val product = Product()// product.name = nullproduct.category = "金融財(cái)務(wù)類"product.gmtCreated = Date()product.gmtModified = Date()println(JSONUtils.toJsonString(product))val name = product.nameprintln("product name is ${name}")val eqName = name == "賬務(wù)系統(tǒng)"println(eqName)name.substring(1)} }上面的代碼可以正確編譯通過(guò)。Kotlin編譯器對(duì)來(lái)自Java的空值name(平臺(tái)類型)放寬了空檢查name.substring(1)。但是這樣的空指針異常仍然會(huì)在運(yùn)行時(shí)拋出來(lái)。
運(yùn)行上面的代碼,我們可以看到輸出:
{"category":"金融財(cái)務(wù)類","gmtCreated":1500050426817,"gmtModified":1500050426817} product name is null falsenull cannot be cast to non-null type java.lang.String kotlin.TypeCastException: null cannot be cast to non-null type java.lang.Stringat com.easy.kotlin.CallingJavaNullSafe.testCallingJavaNullSafe(CallingJavaNullSafe.kt:27)我們沒(méi)有設(shè)置name的值,在Java它就是null。我們?cè)贙otlin代碼中使用了這個(gè)name進(jìn)行計(jì)算,我們可以看出:
val eqName = name == "賬務(wù)系統(tǒng)" println(eqName)可以正確輸出false。這表明Kotlin的判斷字符串是否相等已經(jīng)對(duì)null的情況作了判斷處理,這樣的代碼如果在Java中調(diào)用 name.equals("賬務(wù)系統(tǒng)") 就該拋空指針異常了。
但是當(dāng)我們直接使用name這個(gè)值來(lái)調(diào)用name.substring(1)的時(shí)候,Kotlin編譯器不會(huì)檢查這個(gè)空異常,但是運(yùn)行時(shí)還是要報(bào)錯(cuò)的:null cannot be cast to non-null type java.lang.String。
如果我們不想看到這樣的異常,而是當(dāng)name是null的時(shí)候,安靜的輸出null,直接使用Kotlin中的空安全的調(diào)用 .? :
name?.substring(1)這樣,運(yùn)行的時(shí)候不會(huì)拋出異常,直接安靜的返回null。
平臺(tái)類型
平臺(tái)類型不能在程序中顯式表述,因此在語(yǔ)言中沒(méi)有相應(yīng)語(yǔ)法。 然而,編譯器和 IDE 有時(shí)需要(在錯(cuò)誤信息中、參數(shù)信息中等)顯示他們,所以我們用一個(gè)助記符來(lái)表示他們:
T! : 表示 T 或者 T?
(Mutable) Collection<T>! : 表示 “可以可變或不可變、可空或不可空的 T 的 Java 集合”
Array<(out) T>! : 表示“可空或者不可空的 T(或 T 的子類型)的 Java 數(shù)組”
Kotlin與Java中的類型映射
Kotlin 特殊處理一部分 Java 類型。這樣的類型不是“按原樣”從 Java 加載,而是 映射 到相應(yīng)的 Kotlin 類型。
映射只發(fā)生在編譯期間,運(yùn)行時(shí)表示保持不變。
Java 的原生類型映射到相應(yīng)的 Kotlin 類型:
| byte | kotlin.Byte |
| short | kotlin.Short |
| int | kotlin.Int |
| long | kotlin.Long |
| char | kotlin.Char |
| float | kotlin.Float |
| double | kotlin.Double |
| boolean | kotlin.Boolean |
Java中的一些內(nèi)置類型也會(huì)作相應(yīng)的映射:
| java.lang.Object | kotlin.Any! |
| java.lang.Cloneable | kotlin.Cloneable! |
| java.lang.Comparable | kotlin.Comparable! |
| java.lang.Enum | kotlin.Enum! |
| java.lang.Annotation | kotlin.Annotation! |
| java.lang.Deprecated | kotlin.Deprecated! |
| java.lang.CharSequence | kotlin.CharSequence! |
| java.lang.String | kotlin.String! |
| java.lang.Number | kotlin.Number! |
| java.lang.Throwable | kotlin.Throwable! |
Java 的裝箱原始類型映射到對(duì)應(yīng)的可空Kotlin 類型:
| java.lang.Byte | kotlin.Byte? |
| java.lang.Short | kotlin.Short? |
| java.lang.Integer | kotlin.Int? |
| java.lang.Long | kotlin.Long? |
| java.lang.Character | kotlin.Char? |
| java.lang.Float | kotlin.Float? |
| java.lang.Double | kotlin.Double? |
| java.lang.Boolean | kotlin.Boolean? |
另外,用作類型參數(shù)的Java類型映射到Kotlin中的平臺(tái)類型:
例如,List<java.lang.Integer> 在 Kotlin 中會(huì)成為 List<Int!>。
集合類型在 Kotlin 中可以是只讀的或可變的,因此 Java 集合類型作如下映射:
(下表中的所有 Kotlin 類型都在 kotlin.collections包中):
| Iterator | Iterator | MutableIterator | (Mutable)Iterator! |
| Iterable | Iterable | MutableIterable | (Mutable)Iterable! |
| Collection | Collection | MutableCollection | (Mutable)Collection! |
| Set | Set | MutableSet | (Mutable)Set! |
| List | List | MutableList | (Mutable)List! |
| ListIterator | ListIterator | MutableListIterator | (Mutable)ListIterator! |
| Map | Map | MutableMap | (Mutable)Map! |
| Map.Entry | Map.Entry | MutableMap.MutableEntry | (Mutable)Map.(Mutable)Entry! |
Java 的數(shù)組映射:
| int[] | kotlin.IntArray! |
| String[] | kotlin.Array<(out) String>! |
Kotlin 中使用 Java 的泛型
Kotlin 的泛型與 Java 有點(diǎn)不同。當(dāng)將 Java 類型導(dǎo)入 Kotlin 時(shí),我們會(huì)執(zhí)行一些轉(zhuǎn)換:
| Foo! | Foo | Java 的通配符轉(zhuǎn)換成類型投影 |
| Foo | Foo |
Kotlin與Java 中的數(shù)組
與 Java 不同,Kotlin 中的數(shù)組是非型變的,即 Kotlin 不允許我們把一個(gè) Array<String> 賦值給一個(gè) Array<Any>。
Java 平臺(tái)上,持有原生數(shù)據(jù)類型的數(shù)組避免了裝箱/拆箱操作的開(kāi)銷(xiāo)。
在Kotlin中,對(duì)于每種原生類型的數(shù)組都有一個(gè)特化的類(IntArray、 DoubleArray、 CharArray 等)來(lái)實(shí)現(xiàn)同樣的功能。它們與 Array 類無(wú)關(guān),并且會(huì)編譯成 Java 原生類型數(shù)組以獲得最佳性能。
Java 可變參數(shù)
Java 類有時(shí)聲明一個(gè)具有可變數(shù)量參數(shù)(varargs)的方法來(lái)使用索引。
public class VarArgsDemo<T> {static VarArgsDemo vad = new VarArgsDemo();public static void main(String... agrs) {System.out.println(vad.append("a", "b", "c"));System.out.println(vad.append(1, 2, 3));System.out.println(vad.append(1, 2, "3"));}public String append(T... element) {StringBuilder result = new StringBuilder();for (T e : element) {result.append(e);}return result.toString();} }在Kotlin中,我們使用展開(kāi)運(yùn)算符 * 來(lái)傳遞這個(gè)varargs:
@RunWith(JUnit4::class) class VarArgsDemoTest {@Test fun testVarArgsDemo() {val varArgsDemo = VarArgsDemo<Any?>()val array = arrayOf(0, 1, 2, 3)val result = varArgsDemo.append(*array)println(result)} }運(yùn)行輸出:0123
非受檢異常
在 Kotlin 中,所有異常都是非受檢的(Non-Checked Exceptions),這意味著編譯器不會(huì)強(qiáng)迫你捕獲其中的任何一個(gè)。而在Java中會(huì)要求我們捕獲異常,例如下面的代碼:
也就是說(shuō),我們需要寫(xiě)類似下面的try catch代碼塊:
try {jsonUtils.parseObject("{}"); } catch (Exception e) {e.printStackTrace(); }然而在Kotlin中情況就不是這樣子了:當(dāng)我們調(diào)用一個(gè)聲明受檢異常的 Java 方法時(shí),Kotlin 不會(huì)強(qiáng)迫你做任何事情:
@Test fun testNonCheckedExceptions() {val jsonUtils = JSONUtils()jsonUtils.parseObject("{}") }但是,我們?cè)谶\(yùn)行的時(shí)候,還是會(huì)拋異常:
com.easy.kotlin.CallingJavaNullSafe > testNonCheckedExceptions FAILEDjava.lang.Exception at CallingJavaNullSafe.kt:34Kotlin的不受檢異常,這樣也會(huì)導(dǎo)致運(yùn)行時(shí)拋出異常。關(guān)于異常的處理,該處理的終歸還是要處理的。
對(duì)象方法
Java中的java.lang.Object定義如下:
public class Object {private static native void registerNatives();static {registerNatives();}public final native Class<?> getClass();public native int hashCode();public boolean equals(Object obj) {return (this == obj);}protected native Object clone() throws CloneNotSupportedException;public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}public final native void notify();public final native void notifyAll();public final native void wait(long timeout) throws InterruptedException;public final void wait(long timeout, int nanos) throws InterruptedException {...}public final void wait() throws InterruptedException {wait(0);}protected void finalize() throws Throwable { } }當(dāng) Java 類型導(dǎo)入到 Kotlin 中時(shí),類型 java.lang.Object 的所有引用都成了 Any。Any只聲明了 toString()、hashCode() 和 equals() 函數(shù)。怎樣才能用到 java.lang.Object 的其他成員方法呢?下面我們來(lái)看下。
wait()/notify()
《Effective Java》 第 69 條中建議優(yōu)先使用并發(fā)工具(concurrency utilities)而不是 wait() 和 notify()。因此,類型 Any 的引用沒(méi)有提供這兩個(gè)方法。
如果我們真的需要調(diào)用它們的話,可以將其轉(zhuǎn)換為 java.lang.Object來(lái)使用:
(foo as java.lang.Object).wait()getClass()
要取得對(duì)象的 Java 類,我們可以在類引用上使用 java 擴(kuò)展屬性,它是Kotlin的反射類kotlin.reflect.KClass的擴(kuò)展屬性。
val fooClass = foo::class.java上面的代碼使用了自 Kotlin 1.1 起支持的綁定類引用。我們也可以使用 javaClass 擴(kuò)展屬性。
val fooClass = foo.javaClassclone()
要覆蓋 clone(),需要繼承 kotlin.Cloneable:
class Example : Cloneable {override fun clone(): Any { …… } }要謹(jǐn)慎地改寫(xiě)clone方法。
finalize()
要覆蓋 finalize(),我們只需要聲明它即可,不用再寫(xiě) override關(guān)鍵字:
class C {protected fun finalize() {// 終止化邏輯} }訪問(wèn)靜態(tài)成員
Java 類的靜態(tài)成員會(huì)形成該類的“伴生對(duì)象”。我們可以直接顯式訪問(wèn)其成員。例如:
一個(gè)帶靜態(tài)方法的Java類
public class JSONUtils {public static String toJsonString(Object o) {return JSON.toJSONString(o);} }我們?cè)贙otlin代碼可以直接這樣調(diào)用:
@RunWith(JUnit4::class) class JSONUtilsTest {@Test fun testJSONUtils() {val userService = UserServiceImpl()val user = userService.findByName("admin")Assert.assertTrue(user.name == "admin")val userJson = JSONUtils.toJsonString(user)println(userJson)Assert.assertTrue(userJson == "{\"name\":\"admin\",\"password\":\"admin\"}")} }上面我們提到過(guò),如果是反過(guò)來(lái)調(diào)用,Java調(diào)用Kotlin中的object對(duì)象類中的函數(shù),需要使用object的 對(duì)象名.INSTANCE 來(lái)調(diào)用函數(shù)。
Kotlin與Java 的反射
我們可以使用 instance::class.java、ClassName::class.java 或者 instance.javaClass 通過(guò) java.lang.Class 來(lái)進(jìn)入 Java 的反射類java.lang.Class, 之后我們就可以使用Java中的反射的功能特性了。
代碼示例:
@RunWith(JUnit4::class) class RefectClassTest {@Test fun testGetterSetter() {val product = Product()val pClz = product::class.javaprintln(pClz.canonicalName)pClz.declaredFields.forEach { println(it) }pClz.declaredMethods.forEach {println(it.name);it.parameters.forEach { println(it) }}} }運(yùn)行上面的代碼輸出:
com.easy.kotlin.Product java.lang.Long com.easy.kotlin.Product.id java.lang.String com.easy.kotlin.Product.name java.lang.String com.easy.kotlin.Product.category java.util.Date com.easy.kotlin.Product.gmtCreated java.util.Date com.easy.kotlin.Product.gmtModified getName setName java.lang.String arg0 getId setId java.lang.Long arg0 setCategory java.lang.String arg0 getGmtCreated setGmtCreated java.util.Date arg0 getGmtModified setGmtModified java.util.Date arg0 getCategorySAM 轉(zhuǎn)換
我們?cè)贙otlin中,要某個(gè)函數(shù)做某件事時(shí),會(huì)傳一個(gè)函數(shù)參數(shù)給它。 而在Java中,并不支持傳送函數(shù)參數(shù)。通常Java的實(shí)現(xiàn)方式是將動(dòng)作放在一個(gè)實(shí)現(xiàn)某接口的類中,然后將該類的一個(gè)實(shí)例傳遞給另一個(gè)方法。在大多數(shù)情況下,這些接口只有單個(gè)抽象方法(single abstract method),在Java中被稱為SAM類型。
例如:Runnable接口:
@FunctionalInterface public interface Runnable {public abstract void run(); }在 Java 8中我們也通常稱之為函數(shù)式接口。
Kotlin 支持 SAM 轉(zhuǎn)換。Kotlin 的函數(shù)字面值可以被自動(dòng)的轉(zhuǎn)換成只有一個(gè)非默認(rèn)方法的 Java 接口的實(shí)現(xiàn),只要這個(gè)方法的參數(shù)類型能夠與這個(gè) Kotlin 函數(shù)的參數(shù)類型相匹配。
我們可以這樣創(chuàng)建 SAM 接口的實(shí)例:
val runnable = Runnable { println("執(zhí)行測(cè)試") } // Kotlin 調(diào)用Java的SAM接口方法測(cè)試代碼:
@RunWith(JUnit4::class) class SAMFunctionalInterfaceTest {@Test fun testSAMFunctionalInterface() {val runnable = Runnable { println("執(zhí)行測(cè)試") }val thread = Thread(runnable)thread.start()} }要注意的是,SAM 轉(zhuǎn)換只適用于接口,而不適用于抽象類,即使這些抽象類也只有一個(gè)抽象方法。
還要注意,此功能只適用于 Java 互操作;因?yàn)?Kotlin 具有合適的函數(shù)類型,所以不需要將函數(shù)自動(dòng)轉(zhuǎn)換為 Kotlin 接口的實(shí)現(xiàn)。
Java使用了Kotlin的關(guān)鍵字
一些 Kotlin 關(guān)鍵字在 Java 中是有效標(biāo)識(shí)符:in、 object、 is等等。
如果一個(gè) Java 庫(kù)使用了 Kotlin 關(guān)鍵字作為方法,我們可以通過(guò)反引號(hào)(`)字符轉(zhuǎn)義它來(lái)調(diào)用該方法。例如我們有個(gè)Java類,其中有個(gè)is方法:
public class MathTools {public boolean is(Object o) {return true;}}那么我們?cè)贙otlin代碼這樣調(diào)用這個(gè)is方法:
@RunWith(JUnit4::class) class MathToolsTest {@Test fun testISKeyWord(){val b = MathTools().`is`(1)} }Java 調(diào)用 Kotlin
Java 同樣也可以調(diào)用 Kotlin 代碼。但是要多用一些注解語(yǔ)法。
Java訪問(wèn)Kotlin屬性
Kotlin 屬性會(huì)編譯成以下 Java 元素:
- 一個(gè) getter 方法,名稱通過(guò)加前綴 get 算出;
- 一個(gè) setter 方法,名稱通過(guò)加前綴 set 算出(只適用于 var 屬性);
- 一個(gè)與屬性名稱相同的私有字段。
例如,下面的Kotlin類:
class Department {var id: Long = -1Lvar name: String = "Dept" }會(huì)被編譯成對(duì)應(yīng)的 Java 代碼:
public final class Department {private long id = -1L;@NotNullprivate String name = "Dept";public final long getId() {return this.id;}public final void setId(long var1) {this.id = var1;}@NotNullpublic final String getName() {return this.name;}public final void setName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name = var1;} }我們可以看出,在Kotlin中的Long類型被編譯成Java中的原生的long了。
我們?cè)贘ava代碼這樣調(diào)用:
另外,如果Kotlin的屬性名以 is 開(kāi)頭,則使用不同的名稱映射規(guī)則:
- getter 的名稱直接使用屬性名稱
- setter 的名稱是通過(guò)將 is 替換為 set 獲得。
例如,對(duì)于屬性 isOpen,其 getter 會(huì)稱做 isOpen(),而其 setter 會(huì)稱做 setOpen()。
這一規(guī)則適用于任何類型的屬性,并不僅限于 Boolean。
代碼示例:
Kotlin代碼
class Department {var id: Long = -1Lvar name: String = "Dept"var isOpen:Boolean = truevar isBig:String = "Y" }Java調(diào)用Kotlin的測(cè)試代碼:
@Test public void testProperty() {Department d = new Department();d.setId(1);d.setName("技術(shù)部");d.setBig("Y");d.setOpen(true);Assert.assertTrue(1 == d.getId());Assert.assertTrue("技術(shù)部".equals(d.getName()));Assert.assertTrue("Y".equals(d.isBig()));Assert.assertTrue(d.isOpen());}Java調(diào)用Kotlin的包級(jí)函數(shù)
在 package com.easy.kotlin 包內(nèi)的 KotlinExample.kt 源文件中聲明的所有的函數(shù)和屬性,包括擴(kuò)展函數(shù),都將編譯成一個(gè)名為 com.easy.kotlin.KotlinExampleKt 的 Java 類中的靜態(tài)方法。
代碼示例:
Kotlin的包級(jí)屬性、函數(shù)代碼:
package com.easy.kotlinfun f1() {println("I am f1") }fun f2() {println("I am f2") }val p: String = "PPP"fun String.swap(index1: Int, index2: Int): String {val strArray = this.toCharArray()val tmp = strArray[index1]strArray[index1] = strArray[index2]strArray[index2] = tmpvar result = ""strArray.forEach { result += it }return result }fun main(args: Array<String>) {println("abc".swap(0, 2)) }編譯成對(duì)應(yīng)的Java的代碼:
public final class KotlinExampleKt {@NotNullprivate static final String p = "PPP";public static final void f1() {String var0 = "I am f1";System.out.println(var0);}public static final void f2() {String var0 = "I am f2";System.out.println(var0);}@NotNullpublic static final String getP() {return p;}@NotNullpublic static final String swap(@NotNull String $receiver, int index1, int index2) {Intrinsics.checkParameterIsNotNull($receiver, "$receiver");char[] var10000 = $receiver.toCharArray();Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).toCharArray()");char[] strArray = var10000;char tmp = strArray[index1];strArray[index1] = strArray[index2];strArray[index2] = tmp;Object result = "";char[] $receiver$iv = strArray;for(int var7 = 0; var7 < $receiver$iv.length; ++var7) {char element$iv = $receiver$iv[var7];result = result + element$iv;}return result;}public static final void main(@NotNull String[] args) {Intrinsics.checkParameterIsNotNull(args, "args");String var1 = swap("abc", 0, 2);System.out.println(var1);} }我們可以看到,Kotlin中的擴(kuò)展函數(shù)
fun String.swap(index1: Int, index2: Int): String被編譯成
public static final String swap(@NotNull String $receiver, int index1, int index2)Kotlin中的String. 接收者被當(dāng)做Java方法中的第一個(gè)參數(shù)傳入。
Java調(diào)用Kotlin包級(jí)屬性、函數(shù)的測(cè)試代碼:
@Test public void testPackageFun() {KotlinExampleKt.f1();KotlinExampleKt.f2();System.out.println(KotlinExampleKt.getP());KotlinExampleKt.swap("abc",0,1); }運(yùn)行輸出:
I am f1 I am f2 PPP bac另外,要注意的這里生成的類KotlinExampleKt,我們不能使用new來(lái)創(chuàng)建實(shí)例對(duì)象:
KotlinExampleKt example = new KotlinExampleKt();// 報(bào)錯(cuò)報(bào)如下錯(cuò)誤:
error: cannot find symbolKotlinExampleKt example = new KotlinExampleKt();^symbol: constructor KotlinExampleKt()location: class KotlinExampleKt 1 error在編程中,我們推薦使用Kotlin默認(rèn)的命名生成規(guī)則。如果確實(shí)有特殊場(chǎng)景需要自定義Kotlin包級(jí)函數(shù)對(duì)應(yīng)的生成Java類的名字,我們可以使用 @JvmName 注解修改生成的 Java 類的類名:
@file:JvmName("MyKotlinExample") package com.easy.kotlin fun f3() {println("I am f3") }fun f4() {println("I am f4") }val p2: String = "PPP"測(cè)試代碼:
MyKotlinExample.f3(); MyKotlinExample.f4();實(shí)例字段
我們使用 @JvmField 注解對(duì)Kotlin中的屬性字段標(biāo)注,表示這是一個(gè)實(shí)例字段(Instance Fields),Kotlin編譯器在處理的時(shí)候,將不會(huì)給這個(gè)字段生成getters/setters方法。
class Department {var id: Long = -1Lvar name: String = "Dept"var isOpen: Boolean = truevar isBig: String = "Y"@JvmField var NO = 0 }映射成Java的代碼就是:
public final class Department {private long id = -1L;@NotNullprivate String name = "Dept";private boolean isOpen = true;@NotNullprivate String isBig = "Y";@JvmFieldpublic int NO;public final long getId() {return this.id;}public final void setId(long var1) {this.id = var1;}@NotNullpublic final String getName() {return this.name;}public final void setName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name = var1;}public final boolean isOpen() {return this.isOpen;}public final void setOpen(boolean var1) {this.isOpen = var1;}@NotNullpublic final String isBig() {return this.isBig;}public final void setBig(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.isBig = var1;} }我們?cè)贘ava中調(diào)用的時(shí)候,就直接使用這個(gè)屬性實(shí)例字段NO :
System.out.println(d.NO = 10);靜態(tài)字段
Kotlin中在命名對(duì)象或伴生對(duì)象中聲明的 屬性:
class Department {...companion object {var innerID = "X001"@JvmFieldvar innerName = "DEP"} }innerID、innerName這兩個(gè)字段的區(qū)別在于可見(jiàn)性上:
@NotNull private static String innerID = "X001"; @JvmField @NotNull public static String innerName = "DEP";這個(gè)私有的innerID通過(guò)Companion對(duì)象來(lái)封裝,提供出public的getInnerID() 、setInnerID來(lái)訪問(wèn):
public static final class Companion {@NotNullpublic final String getInnerID() {return Department.innerID;}public final void setInnerID(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");Department.innerID = var1;}private Companion() {}// $FF: synthetic methodpublic Companion(DefaultConstructorMarker $constructor_marker) {this();} }我們?cè)贘ava訪問(wèn)的innerID時(shí)候,是通過(guò)Companion來(lái)訪問(wèn):
Department.Companion.getInnerID()而我們使用@JvmField注解的字段innerName ,Kotlin編譯器會(huì)把它的訪問(wèn)權(quán)限設(shè)置是public的,這樣我們就可以這樣訪問(wèn)這個(gè)屬性字段了:
Department.innerName靜態(tài)方法
Kotlin 中,我還可以將命名對(duì)象或伴生對(duì)象中定義的函數(shù)標(biāo)注為 @JvmStatic,這樣編譯器既會(huì)在相應(yīng)對(duì)象的類中生成靜態(tài)方法,也會(huì)在對(duì)象自身中生成實(shí)例方法。
跟靜態(tài)屬性類似的,我們看下面的代碼示例:
class Department {...companion object {var innerID = "X001"@JvmFieldvar innerName = "DEP"fun getObjectName() = "ONAME"@JvmStaticfun getObjectID() = "OID"} }編譯器編譯之后,反編譯成的對(duì)應(yīng)的Java代碼:
public final class Department {...@JvmStatic@NotNullpublic static final String getObjectID() {return Companion.getObjectID();}public static final class Companion {...@NotNullpublic final String getObjectName() {return "ONAME";}@JvmStatic@NotNullpublic final String getObjectID() {return "OID";}...} }在Java中調(diào)用的代碼如下:
Department.Companion.getObjectID(); // OK Department.Companion.getObjectName(); // OK, 唯一的工作方式 Department.getObjectID(); // ALSO OK Department.getObjectName(); // ERROR這些注解語(yǔ)法是編譯器為了更加方便Java調(diào)用Kotlin代碼提供的一些簡(jiǎn)便技巧。這樣可使得Java中調(diào)用Kotlin代碼更加自然優(yōu)雅些。
可見(jiàn)性
Kotlin 的可見(jiàn)性與Java的可見(jiàn)性的映射關(guān)系如下表所示:
| private | private |
| protected | protected |
| internal | public |
| public | public |
例如下面的Kotlin代碼:
class ProgrammingBook {private var isbn: String = "978-7-111-44250-9"protected var author: String = "Cay"public var name: String = "Core Java"internal var pages: Int = 300private fun findISBN(): String = "978-7-111-44250-9"protected fun findAuthor(): String = "Cay"public fun findName(): String = "Core Java"internal fun findPages(): Int = 300 }對(duì)應(yīng)的Java的代碼是:
public final class ProgrammingBook {private String isbn = "978-7-111-44250-9";@NotNullprivate String author = "Cay";@NotNullprivate String name = "Core Java";private int pages = 300;@NotNullprotected final String getAuthor() {return this.author;}protected final void setAuthor(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.author = var1;}@NotNullpublic final String getName() {return this.name;}public final void setName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name = var1;}public final int getPages$production_sources_for_module_chapter10_interoperability_main() {return this.pages;}public final void setPages$production_sources_for_module_chapter10_interoperability_main(int var1) {this.pages = var1;}private final String findISBN() {return "978-7-111-44250-9";}@NotNullprotected final String findAuthor() {return "Cay";}@NotNullpublic final String findName() {return "Core Java";}public final int findPages$production_sources_for_module_chapter10_interoperability_main() {return 300;} }我們可以看到Kotlin中的可見(jiàn)性跟Java中的基本相同。
生成默認(rèn)參數(shù)值函數(shù)的重載
我們?cè)贙otlin中寫(xiě)一個(gè)有默認(rèn)參數(shù)值的 Kotlin 方法,它會(huì)對(duì)每一個(gè)有默認(rèn)值的參數(shù)都生成一個(gè)重載函數(shù)。這樣的Kotlin函數(shù),在 Java 中調(diào)用的話,只會(huì)有一個(gè)所有參數(shù)都存在的完整參數(shù)簽名方法可見(jiàn)。如果我們希望Java像Kotlin中一樣可以調(diào)用多個(gè)重載,可以使用@JvmOverloads注解。
下面我們來(lái)通過(guò)一個(gè)實(shí)例對(duì)比兩者的區(qū)別:
這是一段Kotlin代碼:
class OverridesFunWithDefaultParams {fun f1(a: Int = 0, b: String = "B") {}@JvmOverloads fun f2(a: Int = 0, b: String = "B") {} }函數(shù)f1 和 f2 都帶有默認(rèn)參數(shù)。測(cè)試代碼如下:
@Test public void testOverridesFunWithDefaultParams() {OverridesFunWithDefaultParams ofdp = new OverridesFunWithDefaultParams();ofdp.f1(1, "a");ofdp.f2();ofdp.f2(2);ofdp.f2(2, "b"); }這就是@JvmOverloads注解的作用,編譯器會(huì)處理這個(gè)注解所標(biāo)注的函數(shù),并為之生成額外的重載函數(shù)給Java調(diào)用。
檢查Kotlin中異常
如上所述,Kotlin 沒(méi)有受檢異常。即像下面像這樣的 Kotlin 函數(shù):
class CheckKotlinException {fun thisIsAFunWithException() {throw Exception("I am an exception in kotlin")} }在Java中調(diào)用,編譯器是不會(huì)檢查這個(gè)異常的:
@Test public void testCheckKotlinException() {CheckKotlinException cke = new CheckKotlinException();cke.thisIsAFunWithException();// Java編譯器不檢查這個(gè)Kotlin中的異常 }當(dāng)然,在運(yùn)行時(shí),這個(gè)異常還是會(huì)拋出來(lái)。然后,如果我們想要在 Java 中調(diào)用它并捕捉這個(gè)異常,我們可以給Kotlin中的函數(shù)加上注解@Throws(Exception::class), 就像下面這樣:
@Throws(Exception::class) fun thisIsAnotherFunWithException() {throw Exception("I am Another exception in kotlin") }然后,我們?cè)贘ava中調(diào)用的時(shí)候,Java編譯器就會(huì)檢查這個(gè)異常:
最后,我們的代碼就需要捕獲該異常并處理它。
完整的示例代碼如下:
package com.easy.kotlinclass CheckKotlinException {fun thisIsAFunWithException() {throw Exception("I am an exception in kotlin")}@Throws(Exception::class)fun thisIsAnotherFunWithException() {throw Exception("I am Another exception in kotlin")} }測(cè)試代碼:
package com.easy.kotlin;import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4;@RunWith(JUnit4.class) public class CheckKotlinExceptionTest {@Testpublic void testCheckKotlinException() {CheckKotlinException cke = new CheckKotlinException();cke.thisIsAFunWithException();// Java編譯器不檢查這個(gè)Kotlin中的異常// Kotlin中顯示聲明了異常,Java編譯器會(huì)檢查這個(gè)異常// cke.thisIsAnotherFunWithException();try {cke.thisIsAnotherFunWithException();} catch (Exception e) {e.printStackTrace();}} }Nothing 類型
在Kotlin中Nothing類型是一個(gè)特殊的類型,它在 Java 中沒(méi)有的對(duì)應(yīng)的類型。在使用 Nothing 參數(shù)的地方會(huì)生成一個(gè)原始類型。
例如下面的Kotlin代碼:
fun emptyList(): List<Nothing> = listOf()對(duì)應(yīng)到Java代碼中是:
@NotNull public final List emptyList() {return CollectionsKt.emptyList(); }Kotlin中的List<Nothing> 映射為原生類型List 。
Kotlin與Java對(duì)比
在前面的內(nèi)容里,我們已經(jīng)看到了Java與Kotlin的互操作的基本方式。為了更好的認(rèn)識(shí)Java與Kotlin這兩門(mén)語(yǔ)言,我們?cè)谶@里給出一些基本功能,同時(shí)使用Java與Kotlin來(lái)實(shí)現(xiàn)的代碼實(shí)例。通過(guò)橫向?qū)Ρ?#xff0c;從中我們可以看出它們的異同。
(此處可整理成表格形式)
打印日志
- Java
- Kotlin
其實(shí),Kotlin中的println函數(shù)是一個(gè)內(nèi)聯(lián)函數(shù),它其實(shí)就是通過(guò)封裝java.lang.System類的System.out.println來(lái)實(shí)現(xiàn)的。
@kotlin.internal.InlineOnly public inline fun print(message: Any?) {System.out.print(message) }常量與變量
- Java
- Kotlin
null聲明
- Java
- Kotlin
空判斷
- Java
- Kotlin
在Kotlin中,我們只使用一個(gè)問(wèn)號(hào)安全調(diào)用符號(hào)就省去了Java中煩人的if - null 判斷。
字符串拼接
- Java
- Kotlin
Kotlin中使用$和${}(花括號(hào)里面是表達(dá)式的時(shí)候)占位符來(lái)實(shí)現(xiàn)字符串的拼接,這個(gè)比在Java中每次使用加號(hào)來(lái)拼接要方便許多。
換行
- Java
- Kotlin
三元表達(dá)式
- Java
- Kotlin
操作符
- java
- Kotlin
類型判斷和轉(zhuǎn)換(顯式)
- Java
- Kotlin
類型判斷和轉(zhuǎn)換 (隱式)
- Java
- Kotlin
Kotlin的類型系統(tǒng)具備一定的類型推斷能力,這樣也省去了不少在Java中類型轉(zhuǎn)換的樣板式代碼。
Range區(qū)間
- Java
- Kotlin
更靈活的case語(yǔ)句
- Java
- Kotlin
for循環(huán)
- Java
- Kotlin
更方便的集合操作
- Java
- Kotlin
遍歷
- Java
- Kotlin
方法(函數(shù))定義
- Java
- Kotlin
帶返回值的方法(函數(shù))
- Java
- Kotlin
另外,Kotlin中的函數(shù)是可以直接傳入函數(shù)參數(shù),同時(shí)可以返回一個(gè)函數(shù)類型的。
constructor 構(gòu)造器
- Java
- Kotlin
JavaBean與Kotlin數(shù)據(jù)類
這段Kotlin中的數(shù)據(jù)類的代碼:
data class Developer(val name: String, val age: Int)對(duì)應(yīng)下面這段Java實(shí)體類的代碼:
- Java
本章小結(jié)
本章我們一起學(xué)習(xí)了Kotlin與Java的互操作,同時(shí)我們用一些簡(jiǎn)單的示例對(duì)比了它們的異同。在這之中,我們能感受到Kotlin的簡(jiǎn)潔、優(yōu)雅。 我們可以用更少的代碼來(lái)實(shí)現(xiàn)更多的功能。 另外,在IDEA中,我們可以直接使用Kotlin插件來(lái)直接進(jìn)行Java代碼與Kotlin代碼之間的轉(zhuǎn)換(雖然,有些情況下需要我們手工再去稍作改動(dòng))。
Kotlin的定位本身之一就是官網(wǎng)首頁(yè)重點(diǎn)強(qiáng)調(diào)的:100% interoperable with Java?。它并不是scala那樣另起爐灶, 將類庫(kù)(例如,集合類)都自己實(shí)現(xiàn)了一遍。kotlin是對(duì)現(xiàn)有Java的增強(qiáng),通過(guò)擴(kuò)展方法給java提供了很多諸如fp之類的特性, 但同時(shí)始終保持對(duì)java的兼容。
而在Java生態(tài)領(lǐng)域最為人知的Spring框架,在最新的Spring 5中對(duì)Kotlin也作了支持(參看:introducing-kotlin-support-in-spring-framework-5-0)。 當(dāng)前,作為Spring大家族中最引人注目的非Spring Boot莫屬了。我們即將在下一章中介紹Kotlin集成Spring Boot來(lái)開(kāi)發(fā)服務(wù)端Web項(xiàng)目。
本章示例代碼:https://github.com/EasyKotlin/chapter10_interoperability
原文鏈接:https://github.com/EasyKotlin
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Kotlin极简教程:第10章 Kotlin与Java互操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Kotlin极简教程:第9章 轻量级线程
- 下一篇: Kotlin极简教程:第4章 基本数据类