1. 程式人生 > >Kotlin極簡教程:第10章 Kotlin與Java互操作

Kotlin極簡教程:第10章 Kotlin與Java互操作

Kotlin is 100% interoperable with Java™ and Android™

在前面的章節中,我們已經學習了Kotlin的基礎語法、型別系統、泛型與集合類、面向物件與函數語言程式設計等主題,在上一章中我們還看到了Kotlin提供的輕量級併發程式設計模型:協程的相關內容。

從本章開始到在後面的章節中,我們將進入工程程式碼的實戰。我們將在後面分別介紹Kotlin整合SpringBoot開發服務端Web專案、使用Kotlin開發Android專案,以及使用Kotlin來寫前端JavaScript程式碼的等主題。

Kotlin 的競爭優勢在於它並不是完全隔離於 Java 語言。它基本上是可與 Java 100% 互操作的。這樣,Kotlin就可以站在整個Java生態巨人的肩上,向著更遠大的前程前進。

本章我們就讓我們一起來學習下Kotlin與Java的互操作。

Kotlin 呼叫 Java示例

Kotlin 很像 Java。它長得不像 Clojure 或者 Scala 那麼奇怪(承認現實把,這兩種語言就是挺奇怪的)。所以我們學 Kotlin 應該很快。這門語言顯然就是寫給 Java 開發者來用的。

Kotlin 在設計之初就考慮了與 Java 的互操作性。我們可以從 Kotlin 中自然地呼叫現存的 Java 程式碼。例如,下面是一個Kotlin呼叫Java中的Okhttp庫的程式碼:

package com.easy.kotlin

import okhttp3.*
import java.io
.File import java.io.IOException import java.util.concurrent.TimeUnit object 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呼叫Java程式碼跟Groovy一樣流暢自如(但是不像Groovy那樣“怎麼寫都對,但是一執行就報錯”,因為Groovy是一門動態型別語言,而Kotlin則是一門強型別的靜態型別語言)。我們基本不需要改變什麼就可以直接使用Java中的API庫。

並且在 Java 程式碼中也可以很順利地呼叫 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);
    }
}

因為Kotlin跟Java本是兩門語言,所以在互相呼叫的時候,會有一些特殊的語法。這裡的使用Java呼叫Kotlin的object物件函式的語法就是OkhttpUtils.INSTANCE.get(url), 我們看到這裡多了個INSTANCE 。

我們甚至也可以在一個專案中同時使用Kotlin和Java兩 種語言混合程式設計。我們可以在下一章中看到,我們在一個SpringBoot工程中同時使用了Kotlin和Java兩種語言進行混合開發。

下面我們來繼續介紹 Kotlin 呼叫 Java 程式碼的一些細節。

Kotlin使用Java的集合類

Kotlin的集合類API很多就是直接使用的Java的API來實現的。我們在使用的時候,毫無違和感,自然天成:

@RunWith(JUnit4::class)
class KotlinUsingJavaTest {
    @Test fun testArrayList() {
        val source = listOf<Int>(1, 2, 3, 4, 5)
        // 使用Java的ArrayList
        val list = ArrayList<Int>()
        for (item in source) {
            list.add(item) // ArrayList.add()
        }
        for (i in 0..source.size - 1) {
            list[i] = source[i] // 呼叫 get 和 set
        }
    }
}

Kotlin呼叫Java中的Getter 和 Setter

在Java中遵循這樣的約定: getter 方法無引數並以 get 開頭,setter 方法單引數並以 set 開頭。在 Kotlin 中我們可以直接表示為屬性。 例如,我們寫一個帶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;
    }
}

然後,我們在Kotlin可以直接使用屬性名字進行get和set操作:

@RunWith(JUnit4::class)
class ProductTest {
    @Test fun testGetterSetter() {
        val product = Product()
        product.name = "賬務系統"
        product.category = "金融財務類"
        product.gmtCreated = Date()
        product.gmtModified = Date()
        println(JSONUtils.toJsonString(product))
        Assert.assertTrue(product.getName() == "賬務系統")
        Assert.assertTrue(product.name == "賬務系統")
        Assert.assertTrue(product.getCategory() == "金融財務類")
        Assert.assertTrue(product.category == "金融財務類")
    }
}

當然,我們還可以像在Java中一樣,直接呼叫像product.getName()、product.setName(“Kotlin”)這樣的getter、setter方法。

呼叫Java中返回 void 的方法

如果一個 Java 方法返回 void,那麼從 Kotlin 呼叫時中返回 Unit

public class Admin {
    String name;

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

    @Override
    public String toString() {
        return "Admin{" +
            "name='" + name + '\'' +
            '}';
    }
}

我們這樣呼叫

val setReturn = admin.setName("root")
println(setReturn)

將輸出:kotlin.Unit

空安全和平臺型別

我們知道Java 中的任何引用都可能是null,這樣我們在使用 Kotlin呼叫來自 Java 的物件的時候就有可能會出現空安全的問題。

Java 宣告的型別在 Kotlin 中會被特別對待並稱為平臺型別(platform types )。對這種型別的空檢查會放寬,因此它們的安全保證與在 Java 中相同。

請看以下示例:

@RunWith(JUnit4::class)
class CallingJavaNullSafe {
    @Test fun testCallingJavaNullSafe() {
        val product = Product()
        // product.name = null
        product.category = "金融財務類"
        product.gmtCreated = Date()
        product.gmtModified = Date()
        println(JSONUtils.toJsonString(product))

        val name = product.name
        println("product name is ${name}")

        val eqName = name == "賬務系統"
        println(eqName)

        name.substring(1)
    }
}

上面的程式碼可以正確編譯通過。Kotlin編譯器對來自Java的空值name(平臺型別)放寬了空檢查name.substring(1)。但是這樣的空指標異常仍然會在執行時丟擲來。

執行上面的程式碼,我們可以看到輸出:

{"category":"金融財務類","gmtCreated":1500050426817,"gmtModified":1500050426817}
product name is null
false

null cannot be cast to non-null type java.lang.String
kotlin.TypeCastException: null cannot be cast to non-null type java.lang.String
    at com.easy.kotlin.CallingJavaNullSafe.testCallingJavaNullSafe(CallingJavaNullSafe.kt:27)

我們沒有設定name的值,在Java它就是null。我們在Kotlin程式碼中使用了這個name進行計算,我們可以看出:

val eqName = name == "賬務系統"
println(eqName)

可以正確輸出false。這表明Kotlin的判斷字串是否相等已經對null的情況作了判斷處理,這樣的程式碼如果在Java中呼叫 name.equals("賬務系統") 就該拋空指標異常了。

但是當我們直接使用name這個值來呼叫name.substring(1)的時候,Kotlin編譯器不會檢查這個空異常,但是執行時還是要報錯的:null cannot be cast to non-null type java.lang.String

如果我們不想看到這樣的異常,而是當name是null的時候,安靜的輸出null,直接使用Kotlin中的空安全的呼叫 .?

name?.substring(1)

這樣,執行的時候不會丟擲異常,直接安靜的返回null。

平臺型別

平臺型別不能在程式中顯式表述,因此在語言中沒有相應語法。 然而,編譯器和 IDE 有時需要(在錯誤資訊中、引數資訊中等)顯示他們,所以我們用一個助記符來表示他們:

  • T! : 表示 T 或者 T?

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

  • Array<(out) T>! : 表示“可空或者不可空的 T(或 T 的子型別)的 Java 陣列”

Kotlin與Java中的型別對映

Kotlin 特殊處理一部分 Java 型別。這樣的型別不是“按原樣”從 Java 載入,而是 對映 到相應的 Kotlin 型別。

對映只發生在編譯期間,執行時表示保持不變。

Java 的原生型別對映到相應的 Kotlin 型別:

Java 型別 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中的一些內建型別也會作相應的對映:

Java 型別 Kotlin 型別
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 的裝箱原始型別對映到對應的可空Kotlin 型別:

Java 型別 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?

另外,用作型別引數的Java型別對映到Kotlin中的平臺型別:
例如,List<java.lang.Integer> 在 Kotlin 中會成為 List<Int!>

集合型別在 Kotlin 中可以是隻讀的或可變的,因此 Java 集合型別作如下對映:
(下表中的所有 Kotlin 型別都在 kotlin.collections包中):

Java 型別 Kotlin 只讀型別 Kotlin 可變型別 載入的平臺型別
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 的陣列對映:

Java 型別 Kotlin 型別
int[] kotlin.IntArray!
String[] kotlin.Array<(out) String>!

Kotlin 中使用 Java 的泛型

Kotlin 的泛型與 Java 有點不同。當將 Java 型別匯入 Kotlin 時,我們會執行一些轉換:

Kotlin 的泛型 Java 的泛型 說明
Foo! Foo Java 的萬用字元轉換成型別投影
Foo Foo

Kotlin與Java 中的陣列

與 Java 不同,Kotlin 中的陣列是非型變的,即 Kotlin 不允許我們把一個 Array<String> 賦值給一個 Array<Any>

Java 平臺上,持有原生資料型別的陣列避免了裝箱/拆箱操作的開銷。

在Kotlin中,對於每種原生型別的陣列都有一個特化的類(IntArrayDoubleArrayCharArray 等)來實現同樣的功能。它們與 Array 類無關,並且會編譯成 Java 原生型別陣列以獲得最佳效能。

Java 可變引數

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

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中,我們使用展開運算子 * 來傳遞這個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)
    }
}

執行輸出:0123

非受檢異常

在 Kotlin 中,所有異常都是非受檢的(Non-Checked Exceptions),這意味著編譯器不會強迫你捕獲其中的任何一個。而在Java中會要求我們捕獲異常,例如下面的程式碼:

Kotlin極簡教程

也就是說,我們需要寫類似下面的try catch程式碼塊:

try {
    jsonUtils.parseObject("{}");
} catch (Exception e) {
    e.printStackTrace();
}

然而在Kotlin中情況就不是這樣子了:當我們呼叫一個宣告受檢異常的 Java 方法時,Kotlin 不會強迫你做任何事情:

@Test fun testNonCheckedExceptions() {
    val jsonUtils = JSONUtils()
    jsonUtils.parseObject("{}")
}

但是,我們在執行的時候,還是會拋異常:

com.easy.kotlin.CallingJavaNullSafe > testNonCheckedExceptions FAILED
    java.lang.Exception at CallingJavaNullSafe.kt:34

Kotlin的不受檢異常,這樣也會導致執行時丟擲異常。關於異常的處理,該處理的終歸還是要處理的。

物件方法

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

當 Java 型別匯入到 Kotlin 中時,型別 java.lang.Object 的所有引用都成了 AnyAny只聲明瞭 toString()hashCode()equals() 函式。怎樣才能用到 java.lang.Object 的其他成員方法呢?下面我們來看下。

wait()/notify()

《Effective Java》 第 69 條中建議優先使用併發工具(concurrency utilities)而不是 wait()notify()。因此,型別 Any 的引用沒有提供這兩個方法。

如果我們真的需要呼叫它們的話,可以將其轉換為 java.lang.Object來使用:

(foo as java.lang.Object).wait()

getClass()

要取得物件的 Java 類,我們可以在類引用上使用 java 擴充套件屬性,它是Kotlin的反射類kotlin.reflect.KClass的擴充套件屬性。

val fooClass = foo::class.java

上面的程式碼使用了自 Kotlin 1.1 起支援的繫結類引用。我們也可以使用 javaClass 擴充套件屬性。

val fooClass = foo.javaClass

clone()

要覆蓋 clone(),需要繼承 kotlin.Cloneable


class Example : Cloneable {
    override fun clone(): Any { …… }
}

要謹慎地改寫clone方法。

finalize()

要覆蓋 finalize(),我們只需要宣告它即可,不用再寫 override關鍵字:

class C {
    protected fun finalize() {
        // 終止化邏輯
    }
}

訪問靜態成員

Java 類的靜態成員會形成該類的“伴生物件”。我們可以直接顯式訪問其成員。例如:

一個帶靜態方法的Java類

public class JSONUtils {
    public static String toJsonString(Object o) {
        return JSON.toJSONString(o);
    }
}

我們在Kotlin程式碼可以直接這樣呼叫:

@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\"}")
    }
}

上面我們提到過,如果是反過來呼叫,Java呼叫Kotlin中的object物件類中的函式,需要使用object的 物件名.INSTANCE 來呼叫函式。

Kotlin與Java 的反射

我們可以使用 instance::class.javaClassName::class.java 或者 instance.javaClass 通過 java.lang.Class 來進入 Java 的反射類java.lang.Class, 之後我們就可以使用Java中的反射的功能特性了。

程式碼示例:

@RunWith(JUnit4::class)
class RefectClassTest {
    @Test fun testGetterSetter() {
        val product = Product()
        val pClz = product::class.java
        println(pClz.canonicalName)
        pClz.declaredFields.forEach { println(it) }
        pClz.declaredMethods.forEach {
            println(it.name);
            it.parameters.forEach { println(it) }
        }
    }
}

執行上面的程式碼輸出:

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
getCategory

SAM 轉換

我們在Kotlin中,要某個函式做某件事時,會傳一個函式引數給它。 而在Java中,並不支援傳送函式引數。通常Java的實現方式是將動作放在一個實現某介面的類中,然後將該類的一個例項傳遞給另一個方法。在大多數情況下,這些介面只有單個抽象方法(single abstract method),在Java中被稱為SAM型別。

例如:Runnable介面:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

在 Java 8中我們也通常稱之為函式式介面。

Kotlin 支援 SAM 轉換。Kotlin 的函式字面值可以被自動的轉換成只有一個非預設方法的 Java 介面的實現,只要這個方法的引數型別能夠與這個 Kotlin 函式的引數型別相匹配。

我們可以這樣建立 SAM 介面的例項:

val runnable = Runnable { println("執行測試") } // Kotlin 呼叫Java的SAM介面方法

測試程式碼:

@RunWith(JUnit4::class)
class SAMFunctionalInterfaceTest {
    @Test fun testSAMFunctionalInterface() {
        val runnable = Runnable { println("執行測試") }
        val thread = Thread(runnable)
        thread.start()
    }
}

要注意的是,SAM 轉換隻適用於介面,而不適用於抽象類,即使這些抽象類也只有一個抽象方法。

還要注意,此功能只適用於 Java 互操作;因為 Kotlin 具有合適的函式型別,所以不需要將函式自動轉換為 Kotlin 介面的實現。

Java使用了Kotlin的關鍵字

一些 Kotlin 關鍵字在 Java 中是有效識別符號:in、 object、 is等等。

如果一個 Java 庫使用了 Kotlin 關鍵字作為方法,我們可以通過反引號(`)字元轉義它來呼叫該方法。例如我們有個Java類,其中有個is方法:

public class MathTools {

    public boolean is(Object o) {
        return true;
    }

}

那麼我們在Kotlin程式碼這樣呼叫這個is方法:

@RunWith(JUnit4::class)
class MathToolsTest {
    @Test fun testISKeyWord(){
        val b = MathTools().`is`(1)
    }
}

Java 呼叫 Kotlin

Java 同樣也可以呼叫 Kotlin 程式碼。但是要多用一些註解語法。

Java訪問Kotlin屬性

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

  • 一個 getter 方法,名稱通過加字首 get 算出;
  • 一個 setter 方法,名稱通過加字首 set 算出(只適用於 var 屬性);
  • 一個與屬性名稱相同的私有欄位。

例如,下面的Kotlin類:

class Department {
    var id: Long = -1L
    var name: String = "Dept"
}

會被編譯成對應的 Java 程式碼:

public final class Department {
   private long id = -1L;
   @NotNull
   private String name = "Dept";

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }
}

我們可以看出,在Kotlin中的Long型別被編譯成Java中的原生的long了。
我們在Java程式碼這樣呼叫:

@RunWith(JUnit4.class)
public class JavaCallingKotlinCodeTest {
    @Test
    public void testProperty() {
        Department d = new Department();
        d.setId(1);
        d.setName("技術部");

        Assert.assertTrue(1 == d.getId());
        Assert.assertTrue("技術部".equals(d.getName()));

    }
}

另外,如果Kotlin的屬性名以 is 開頭,則使用不同的名稱對映規則:

  • getter 的名稱直接使用屬性名稱
  • setter 的名稱是通過將 is 替換為 set 獲得。

例如,對於屬性 isOpen,其 getter 會稱做 isOpen(),而其 setter 會稱做 setOpen()

這一規則適用於任何型別的屬性,並不僅限於 Boolean

程式碼示例:

Kotlin程式碼

class Department {
    var id: Long = -1L
    var name: String = "Dept"
    var isOpen:Boolean = true
    var isBig:String = "Y"
}

Java呼叫Kotlin的測試程式碼:

@Test
public void testProperty() {
    Department d = new Department();
    d.setId(1);
    d.setName("技術部");
    d.setBig("Y");
    d.setOpen(true);

    Assert.assertTrue(1 == d.getId());
    Assert.assertTrue("技術部".equals(d.getName()));
    Assert.assertTrue("Y".equals(d.isBig()));
    Assert.assertTrue(d.isOpen());

}

Java呼叫Kotlin的包級函式

package com.easy.kotlin 包內的 KotlinExample.kt 原始檔中宣告的所有的函式和屬性,包括擴充套件函式,都將編譯成一個名為 com.easy.kotlin.KotlinExampleKt 的 Java 類中的靜態方法。

程式碼示例:

Kotlin的包級屬性、函式程式碼:

package com.easy.kotlin

fun 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] = tmp

    var result = ""
    strArray.forEach { result += it }
    return result
}

fun main(args: Array<String>) {
    println("abc".swap(0, 2))
}

編譯成對應的Java的程式碼:

public final class KotlinExampleKt {
   @NotNull
   private 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);
   }

   @NotNull
   public static final String getP() {
      return p;
   }

   @NotNull
   public 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中的擴充套件函式

fun String.swap(index1: Int, index2: Int): String

被編譯成

public static final String swap(@NotNull String $receiver, int index1, int index2)

Kotlin中的String. 接收者被當做Java方法中的第一個引數傳入。

Java呼叫Kotlin包級屬性、函式的測試程式碼:

@Test
public void testPackageFun() {
    KotlinExampleKt.f1();
    KotlinExampleKt.f2();
    System.out.println(KotlinExampleKt.getP());
    KotlinExampleKt.swap("abc",0,1);
}

執行輸出:

I am f1
I am f2
PPP
bac

另外,要注意的這裡生成的類KotlinExampleKt,我們不能使用new來建立例項物件:

KotlinExampleKt example = new KotlinExampleKt();// 報錯

報如下錯誤:

error: cannot find symbol
        KotlinExampleKt example = new KotlinExampleKt();
                                  ^
  symbol:   constructor KotlinExampleKt()
  location: class KotlinExampleKt
1 error

在程式設計中,我們推薦使用Kotlin預設的命名生成規則。如果確實有特殊場景需要自定義Kotlin包級函式對應的生成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"

測試程式碼:

MyKotlinExample.f3();
MyKotlinExample.f4();

例項欄位

我們使用 @JvmField 註解對Kotlin中的屬性欄位標註,表示這是一個例項欄位(Instance Fields),Kotlin編譯器在處理的時候,將不會給這個欄位生成getters/setters方法。

class Department {
    var id: Long = -1L
    var name: String = "Dept"
    var isOpen: Boolean = true
    var isBig: String = "Y"

    @JvmField var NO = 0
}

對映成Java的程式碼就是:

public final class Department {
   private long id = -1L;
   @NotNull
   private String name = "Dept";
   private boolean isOpen = true;
   @NotNull
   private String isBig = "Y";
   @JvmField
   public int NO;

   public final long getId() {
      return this.id;
   }

   public final void setId(long var1) {
      this.id = var1;
   }

   @NotNull
   public 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;
   }

   @NotNull
   public final String isBig() {
      return this.isBig;
   }

   public final void setBig(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.isBig = var1;
   }
}

我們在Java中呼叫的時候,就直接使用這個屬性例項欄位NO

System.out.println(d.NO = 10);

靜態欄位

Kotlin中在命名物件或伴生物件中宣告的 屬性:

class Department {
    ...
    companion object {
        var innerID = "X001"
        @JvmField
        var innerName = "DEP"
    }
}

innerID、innerName這兩個欄位的區別在於可見性上:

@NotNull
private static String innerID = "X001";
@JvmField
@NotNull
public static String innerName = "DEP";

這個私有的innerID通過Companion物件來封裝,提供出public的getInnerID() 、setInnerID來訪問:

public static final class Companion {
   @NotNull
   public final String getInnerID() {
      return Department.innerID;
   }

   public final void setInnerID(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      Department.innerID = var1;
   }

   private Companion() {
   }

   // $FF: synthetic method
   public Companion(DefaultConstructorMarker $constructor_marker) {
      this();
   }
}

我們在Java訪問的innerID時候,是通過Companion來訪問:

Department.Companion.getInnerID()

而我們使用@JvmField註解的欄位innerName ,Kotlin編譯器會把它的訪問許可權設定是public的,這樣我們就可以這樣訪問這個屬性欄位了:

Department.innerName

靜態方法

Kotlin 中,我還可以將命名物件或伴生物件中定義的函式標註為 @JvmStatic,這樣編譯器既會在相應物件的類中生成靜態方法,也會在物件自身中生成例項方法。

跟靜態屬性類似的,我們看下面的程式碼示例:

class Department {
    ...
    companion object {
        var innerID = "X001"
        @JvmField
        var innerName = "DEP"

        fun getObjectName() = "ONAME"
        @JvmStatic
        fun getObjectID() = "OID"
    }
}