Kotlin和Java互相調(diào)用(一)

Kotlin 調(diào)用 Java

由于 Kotlin本身并沒有提供強(qiáng)大的類庫支持查辩,Kotlin只是一種語言,因此 Kotlin 調(diào)用 Java 通常都是自然而然的事情裳仆。正如我們在前面程序中所看到的汇跨,在 Kotlin 中調(diào)用Java 的 Date、 JFrame 等類完全沒有任何問題 虑乖。
Kotlin調(diào)用 Java時(shí)只是有一些小小的注意點(diǎn),下面對這些內(nèi)容做一下簡單的說明晾虑。

屬性

Kotlin調(diào)用屬性實(shí)際上就是訪問 getter疹味、 setter方法,因此Java類只要提供了 getter方法帜篇, Kotlin就可將其當(dāng)成只讀屬性 ;如果 Java 類提供了 getter糙捺、setter 方法,Kotlin 就可將其當(dāng)成讀寫屬性笙隙。有沒有這個(gè)成員變量已經(jīng)不重要了洪灯。

如果 getter方法的返回值類型是 boolean,該getter方法名也以is開頭,此時(shí)Kotlin會(huì)將其當(dāng)成屬性名與 getter方法同名的屬性竟痰。比如 boolean isMarried()方法可作為 Kotlin 的只讀屬性签钩,該屬性名為 isMarried。

void 和調(diào)用名為關(guān)鍵字的成員

如果 Java方法的返回值是 void坏快, 在 Kotlin 中則對應(yīng)于 Unit返回值類型铅檩。

由于 Kotlin 的關(guān)鍵字比 Java 多(比如 Kotlin 的 is、 object莽鸿、 in 在Java 語言中都不是關(guān)鍵字)昧旨,因此可能出現(xiàn)一種情況 : Java 的類名、接口名祥得、方法名等是 Kotlin的關(guān)鍵字臼予。此時(shí)就需要使用反引號(就是鍵盤上數(shù)字1左邊的那個(gè)鍵)對關(guān)鍵字進(jìn)行轉(zhuǎn)義。

public class InMethod {
    public void  in(){
        System.out.println("in");
    }

}
fun main(args: Array<String>) {
    val im = InMethod()
    //對 in 關(guān)鍵字轉(zhuǎn)義
    im.`in`()
}

Kotlin 的己映射類型

表 12.1 顯示了 Java基本類型與 Kotlin類之間的映射關(guān)系啃沪。


image.png

Java基本類型的包裝類則映射到 Kotlin類的可空類型粘拾,如表 12.2所示。


image.png

此外创千, Kotlin還為 Java的一些常用類提供了映射缰雇,如表 12.3所示。
image.png

Kotlin 對 Java 泛型的轉(zhuǎn)換

Kotlin 不支持 Java 的泛型通配符語法追驴,但 Kotlin 提供了使用處型變來代替泛型通配符械哟。
因此 Java 的通配符將會(huì)轉(zhuǎn)換為Kotlin的使用處型變(類型投影)。此外java的原始類型則轉(zhuǎn)換為 Kotlin的星號投影殿雪。它們的關(guān)系如表 12.4 所示暇咆。


image.png

Kotlin和 Java 一樣,它們都無法在運(yùn)行時(shí)保留泛型信息,因此在 Kotlin中使用is 運(yùn)算符進(jìn)行類型檢測時(shí)只能檢測星號投影(相當(dāng)于 Java 的原始類型)爸业,不能檢測泛型信息其骄。例如如下代碼。

fun main(args: Array<String>) {
    val list = listOf(2,3,10)
    //is判斷不能檢測泛型參數(shù)
   // println(list is List<String>)
    //只能檢測星號投影
    //輸出true
    println(list is List<*>)
}

正如從上面程序中所看到的扯旷,程序檢測 list is List<String>是無效的拯爽,這是因?yàn)槌绦蛟谶\(yùn)行時(shí)只能判斷是否為 List,無法判斷它的泛型信息钧忽。

對Java數(shù)組的處理

相比 Java數(shù)組毯炮,Kotlin數(shù)組有一個(gè)重大的改變:Kotlin數(shù)組是不型變的。因此 Array<String>不能賦值給 Array<Object>變量耸黑。 但 Java 數(shù)組不同桃煎,Java 數(shù)組是型變的,因此 String[]可以直接賦值給 Object[]變量大刊。

此外备禀, Java還支持 int[]、 long[]等基本類型的數(shù)組奈揍,這種數(shù)組可以避免拆箱、裝箱帶來的性能開銷赋续,但 Kotlin 的 Array 類不支持這種基本類型的數(shù)組男翰。為此, Kotlin 提供了 ByteArray纽乱、 ShortArray蛾绎、 IntArray、 LongArray鸦列、 CharArray租冠、 FloatArray、 DoubleArray薯嗤、 BooleanArray數(shù)組顽爹, 這幾種數(shù)組專門用于代替 Java的 byte[]、short[]等基本類型的數(shù)組骆姐。比如定義了如下 Java方法镜粤。

public class InMethod {
    public int sum(int [] array) {
        int sum =0;
        for (int i = 0; i < array.length; i++) {
            sum+=array[i];
        }
        return sum;
    }
}

對應(yīng)的kotlin代碼

class InMethod {
    fun sum(array: IntArray): Int {
        var sum = 0
        for (i in array.indices) {
            sum += array[i]
        }
        return sum
    }
}

此外 ,讀者無須對 Kotlin數(shù)組訪問效率有任何擔(dān)心玻褪, Kotlin編譯器會(huì)對數(shù)組訪問進(jìn)行優(yōu)化肉渴, 它會(huì)采用和 Java 相似的方式來訪問數(shù)組元素 Java 訪問數(shù)組元素的方式是:根據(jù)索引直接計(jì)算數(shù)組元素的內(nèi)存地址,訪問效率非常高带射,因此 Kotlin 在訪問元素時(shí)沒有性能損耗 同规。

fun main(args: Array<String>) {
    val array = arrayOf(1, 2, 3, 4)
    //不會(huì)使用 get ()、 set ()方法訪問數(shù)組元素
    ///實(shí)際上還是通過 Java 數(shù)組的快速訪問
    array[1] = array[1] * 2

    //使用索引定位是基于 Java 數(shù)組元素所在內(nèi)存地址的快速訪問
    //不需要?jiǎng)?chuàng)建迭代器
    for (i in array.indices) {
        array[i] += 2

    }

    //for-in 循環(huán)也是基于 Java 數(shù)組元素所在內(nèi)存地址的快速訪問
    //不需要?jiǎng)?chuàng)建迭代器
    for (i in array) {
        println(i)
    }
}

正如從上面程序中所看到的,不管程序采用哪種方式來遍歷數(shù)組的元素券勺, Kotlin總會(huì)將其優(yōu)化成和 Java類似的訪問數(shù)組元素的方式绪钥,因此開發(fā)者無須擔(dān)心性能問題。

調(diào)用參數(shù)個(gè)數(shù)可變的方法

對于參數(shù)個(gè)數(shù)可變的方法朱灿, Java可以直接傳入一個(gè)數(shù)組昧识,但 Kotlin不行。 Kotlin要求只能傳入多個(gè)參數(shù)值盗扒,但也可通過使用“*”解開數(shù)組的方式來傳入多個(gè)數(shù)組元素作為參數(shù)值跪楞。
例如,如下程序定義了一個(gè) Java方法侣灶。

public class Test {
    public void test(int... nums) {
       //定義參數(shù)個(gè)數(shù)可變的方法
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }
    }
}

對于上面的 test()方法甸祭,程序可直接傳入多個(gè) int 值來調(diào)用該方法;但如果程序己有一個(gè)IntArray數(shù)組,則需要使用“*”將數(shù)組解開成多個(gè)數(shù)組元素傳入 褥影。

fun main(args: Array<String>) {
    val test = Test()
    val intArray = intArrayOf(1,3,5,7,9)
    //將數(shù)組解開成多個(gè)元素
    test.test(*intArray)
}

checked 異常

由于 Kotlin沒有 checked 異常池户,因此對于 Java 中可能引發(fā) checked 異常的方法、構(gòu)造器 凡怎, Kotlin 則不會(huì)引發(fā)該異常校焦, Kotlin 既可捕獲該異常,也可完全不理會(huì)該異常(無須使用 throws 聲 明拋出異常)统倒。例如如下程序寨典。

fun main(args: Array<String>) {
    //下面代碼可能引發(fā) IOException (checked 異常)
    //fl. Kotlin 并不強(qiáng)制處理該異常
    val fos = FileInputStream("texr.txt")
    println(fos.read())
}

Object 的處理

Java 的Object對應(yīng)于 Kotlin中的Any,又因?yàn)?Any 只聲明了 toString()房匆、hashCode()
和 equals()方法耸成,因此需要考慮如何使用Object 類中其他方法的問題。

1. wait() / notify() / notifyAll()

這三個(gè)方法都是 Java 程序中同步監(jiān)視器支持調(diào)用的方法浴鸿, 一般來說井氢,只有在線程通信中才會(huì)用到,而且 Java提供了更好的Condition來控制線程通信 岳链, 因此一般不需要調(diào)用這三個(gè)方法花竞。但如果編程時(shí)真的需要讓同步監(jiān)視器調(diào)用這三個(gè)方法,則可以在程序中將 Any 轉(zhuǎn)型為Object掸哑,然后再調(diào)用這 三個(gè)方法左胞。例如如下代碼:

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

2. getClass()

Object 對象的 getClass()方法用于獲取該對象的 Java 類, Kotlin 的對象則有兩種方式來獲取該對象的 Java類举户。例如如下代碼:

//獲取 obj 對象的 Java 類
obj::class.java

obj.javaClass

3. clone()

如果要讓對象重寫 cloneO方法烤宙,則需要讓該類實(shí)現(xiàn)kotlin.Cloneable接口 。例如如下代碼:

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

4. finalize()

Object 的 finalize()方法主要用于完成一些資源清理的工作俭嘁,GC 在回收某個(gè)對象之前躺枕,JVM 會(huì)自動(dòng)調(diào)用該對象的 finalize()方法。 如果要重寫 finalize()方法, 則只要在類中實(shí)現(xiàn)該方法即可拐云,不需要使用 override 關(guān)鍵字(因?yàn)樵?Any 類中并沒有聲明 finalize()方法)罢猪。根據(jù) Java 的規(guī)則, finalize()方法不能定義成 private叉瘩。 例如如下代碼 :

class Example {
    protected fun finalize() {
        ///實(shí)現(xiàn)資源清理的邏輯
    }
}

訪問靜態(tài)成員

雖然 Kotlin本身沒有提供 static 關(guān)鍵字膳帕,但 Kotlin提供了伴生對象來實(shí)現(xiàn)靜態(tài)成員,因此Java類中的靜態(tài)成員都可通過伴生對象的語法來調(diào)用薇缅。

fun main(args: Array<String>) {
//調(diào)用 Runtime 的靜態(tài)方法(就像調(diào)用伴生對象的方法一樣〉
    val rt = Runtime.getRuntime()
    println(rt)
    // 訪問 java . awt .BorderLayout 的 NORTH 靜態(tài)成員〈就像訪問伴生對象的屬性一樣)
    val str =java.awt.BorderLayout.NORTH
    println(str)
}

SAM轉(zhuǎn)換

Java 8支持使用 Lambda表達(dá)式來作為函數(shù)式接口的實(shí)例 (這種機(jī)制被稱為SAM 轉(zhuǎn)換)危彩,Kotlin同樣支持這個(gè)功能。 比如如下代碼泳桦。

fun main(args: Array<String>) {
    //使用 Lambda 表達(dá)式來創(chuàng)建函數(shù)式接口(Predicate)的對象
    val pred = Predicate<Int> { t -> t > 5 }
    val list = arrayListOf(3, 5, 7, 200)
    //使用 pred 對 List 集合進(jìn)行過濾
    //輸出[3, 5]
    list.removeIf(pred)
    println(list)


    //使用 Lambda 表達(dá)式來創(chuàng)建函數(shù)式接口( Runnable)的對象
    val rn = Runnable {
        for (i in 0..10) {
            println(i)
        }
    }

    //通過 Runnable 對象創(chuàng)建 汤徽、 啟動(dòng)線程
    Thread(rn).start()

    val excutor = ThreadPoolExecutor(
            5,
            10,
            0L,
            TimeUnit.MILLISECONDS,
            LinkedBlockingQueue<Runnable>()
    )

    //由于 executor 的 execute ( ) 方法需要 一個(gè) Runnable 對象
    //因此程序可自動(dòng)將符合該接口規(guī)范的 Lambda 表達(dá)式創(chuàng)建成 Runnable 對象
    excutor.execute {
        println("This runs in a thread pool")
    }

    //當(dāng)然也可在方法中顯式指定 Lambda 表達(dá)式創(chuàng)建的是 Runnable 對象
    excutor.execute(Runnable {
        println("This runs in a thread pool")
    }) 

}

在 Kotlin 中使用 JNI

如果要在 Java 中使用JNI,則應(yīng)該使用 native 修飾該方法灸撰,表明該方法將會(huì)交給平臺(tái)本地的C或 C++代碼來實(shí)現(xiàn) 谒府。
Kotlin 同樣支持該機(jī)制,只不過 Kotlin 不使用native關(guān)鍵字浮毯,而是使用external 關(guān)鍵字完疫。例如如下函數(shù):

external fun foo(x: Int): Double

該函數(shù)使用了 external修飾,因此該函數(shù)不能指定函數(shù)體债蓝。 該函數(shù)的函數(shù)體將會(huì)由平臺(tái)本地的C 或 C++代碼來實(shí)現(xiàn)壳鹤。

Java調(diào)用 Kotlin

由于 Kotlin程序編譯之后本身就是完全兼容JVM規(guī)范的,因此 Java調(diào)用 Kotlin也是非常方便的 惦蚊。只是有一些小的注意點(diǎn)。

屬性

前面在介紹Kotlin屬性時(shí)己經(jīng)講過讯嫂, Kotlin屬性可編譯成如下三個(gè)成員蹦锋。

  • 一個(gè) private實(shí)例變量,實(shí)例變量名與屬性名相同(如果該屬性具有幕后字段) 欧芽。
  • 一個(gè) getter方法莉掂,方法名為屬性名添加 get前綴 。
  • 一個(gè) setter方法千扔,方法名為屬性名添加 set前綴(讀寫屬性才有 setter方法) 憎妙。

例如如下屬性:

var name: String

該屬性將會(huì)被 Kotlin編譯成如下三個(gè)成員:

    private String name;
    
    public String getName() {
        return name;
    }

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

但如果屬性名以is開頭(屬性類型并不要求一定是Boolean類型〉再沧, 那么該屬性對應(yīng)的生成規(guī)則略有差別杨拐。

  • 實(shí)例變量名 、 getter方法名與屬性名相同鹃操。
  • setter方法名為屬性名去掉 is 前綴龙誊,添加 set前綴抚垃。

包級函數(shù)

所謂包級函數(shù),是指在 Kotlin中直接定義的頂級函數(shù)(不在任何類中定義)。但實(shí)際上 Kotlin編譯器會(huì)為整個(gè) Kotlin源文件生成一個(gè)類(只要該源文件中包含了頂級函數(shù)鹤树、頂級變量)铣焊, 而這些頂級函數(shù)、頂級變量都會(huì)變成該類的靜態(tài)方法罕伯、靜態(tài)變量曲伊。 例如Toplevel.kt中的如下代碼。

var name: String = ""
fun test() {}

由于上面源文件中包含了頂級成員(頂級函數(shù)或變量)追他,在默認(rèn)情況下坟募,Kotlin 編譯器會(huì)為該源文件生成一個(gè) TopLevelKt.class 文件,這些頂級成員就變成該 TopLevelKt 的靜態(tài)成員湿酸。 例如婿屹,上面兩個(gè)頂級成員變成如下靜態(tài)成員 :

public class TopLevelKt {
    private static java.lang.String name;

    public static final java.lang.String getName() {...}

    public static final void setName(java.lang.String) {...}

    public static final void test() { ...}
}

理解了Kotlin 的頂級成員編譯之后的形式,讀者自然也就知道如何調(diào)用它們了推溃。在默認(rèn)情況下昂利, Kotlin 為包含頂級成員的源文件所生成類的類名總是文件名+Kt 后綴,但 Kotlin也允許使用@JvmName 注解(該注解用來修飾文件本身,它是一個(gè) AnnotationRetention.SOURCE 的注解)來改變 Kotlin 編譯生成的類名铁坎。

//指定生成的類
@file:JvmName ("FkUtils")
package org.crazyit

fun test() {}

上面代碼指定該源文件所生成的類名為FkUtils蜂奸, 因此編譯該源文件將會(huì)生成一個(gè) FkUtils.class文件。

還有一種極端情況硬萍,就是多個(gè) Kotlin源文件要生成相同的Java類(包名相同且類名相同扩所,或指定了相同的@JvmName注解),這種情況默認(rèn)會(huì)引發(fā)錯(cuò)誤朴乖。但 Kotlin非常智能祖屏,我們可以指定 Kotlin為這些 Kotlin源文件統(tǒng)一生成一個(gè)Java類,而該Java類將會(huì)包含不同源文件中的頂級成員买羞。為了實(shí)現(xiàn)這種效果袁勺,需要使用 @JvmMultifileClass 注解。
Demo2.kt中

//指定生成的類
@file:JvmName("CiUtils")
@file:JvmMultifileClass
package test11

fun foo1(){
    println("1111")
}

Demo3.kt中

//指定生成的類
@file:JvmName("CiUtils")
@file:JvmMultifileClass
package test11

fun foo2(){
    println("2222")
}

上面兩個(gè) Kotlin 源程序都需要生成 test11.CiUtils 類畜普,在默認(rèn)情況下這將導(dǎo)致錯(cuò)誤期丰。因 此,上面程序使用了@JvmMultifileClass注解吃挑,這就會(huì)告訴 Kotlin編譯器將兩個(gè)文件合并到一個(gè)類中钝荡。

接下來 Java 程序可采用如下方式調(diào)用里面的方法。

public static void main(String[] args){
    CiUtils.foo1();
    CiUtils.foo2();
}

實(shí)例變量

Kotlin 允許將屬性暴露成實(shí)例變量舶衬,只要在程序中使用@JvmField修飾該屬性即可埠通,暴露出來的屬性將會(huì)和 Kotlin屬性具有相同的訪問權(quán)限。
使用@JvmField將屬性暴露成實(shí)例變量的要求如下逛犹。

  • 該屬性具有幕后字段植阴。
  • 該屬性必須是非 private訪問控制的蟹瘾。
  • 該屬性不能使用 open、 override掠手、 const修飾憾朴。
  • 該屬性不能是委托屬性。

例如如下 Kotlin程序喷鸽。

class InstanceField (name : String) {
      @JvmField val myName = name
}

上面代碼定義了一個(gè)屬性众雷,并使用@JvmField注解修飾了該屬性,這樣系統(tǒng)就會(huì)把該屬性的幕后字段暴露成實(shí)例變量(默認(rèn)是 public 訪問權(quán)限)做祝。

下面 Java程序示范了訪問該類的對象的實(shí)例變量砾省。

    public static void main(String[] args) {
        InstanceField ins = new InstanceField ("Kotlin");
        //訪問 InstanceField對象的實(shí)例變量
        ins.myName;
    }

類變量

在命名對象(對象聲明)或伴生對象中聲明的屬性會(huì)在該命名對象或包含伴生對象的類中具有靜態(tài)幕后字段(類變量)。但這些類變量通常是 private訪問權(quán)限混槐,程序可通過如下三種方式之一將它們暴露出來编兄。

  • 使用@JvmField注解修飾。
  • 使用 lateinit 修飾声登。
  • 使用 const 修飾狠鸳。

如下程序在 MyClass 類中定義了一個(gè)伴生對象。

class MyClass{
    //使用 companion 修飾的伴生對象
    companion object MyObject{
        @JvmField val name ="name屬性值"
    }
}

上面代碼在伴生對象中定義了一個(gè)屬性悯嗓。根據(jù)前面的介紹我們知道:伴生對象本來就是用來彌補(bǔ) Kotlin 沒有 static 關(guān)鍵字的不足的件舵,因此伴生對象中的屬性實(shí)際上就相當(dāng)于 MyClass 的類變量,但它默認(rèn)是 private 訪問權(quán)限 脯厨。上面代碼使用了@JvmField 修飾铅祸,這樣該類變量就變成了與該屬性具有相同的訪問控制符: public。

    public static void main(String[] args) {
        //訪問 MyClass 的 name 類變量
        System.out.println(MyClass.name);
    }

在命名對象或伴生對象中的延遲初始化屬性 (lateinit)具有與該屬性的 setter 方法相同的訪問控制符的類變量合武。道理很簡單: Java 并不支持命名對象临梗,因此 Kotlin 的命名對象在 Java 中其實(shí)表現(xiàn)為一個(gè)單例類,命名對象的屬性就變成了類變量:又由于 lateinit屬性需要等到使用時(shí)才賦值稼跳,因此 Kotlin必須將該類變量暴露出來盟庞,否則 Java程序?qū)o法對該類變量賦值。

class MyClass {
    //定義命名對象
    object MyObject{
        //定義延遲初始化屬性
        lateinit var name: String
    }
}

上面程序在 MyObject 命名對象中定義了延遲初始化屬性岂贩,該屬性將會(huì)被暴露成類變量茫经, 因此 Java程序可按如下方式來調(diào)用它巷波。

    public static void main(String[] args) {
        //訪問 MyClass 的 name 類變量
        MyClass.MyObject.name = "sss";
        System.out.println(MyClass.MyObject.name);
    }

此外萎津,在 Kotlin程序中使用 const修飾的屬性,不管是在頂層定義的屬性抹镊,還是在對象中定義的屬性锉屈,只要使用了const修飾,它就會(huì)變成有 public static final 修飾的類變量垮耳。
例如如下 Kotlin程序颈渊。

const val MAX =100

object Obj{
    const val name ="kotlin"
}

class MyClass{
    companion object{
        //使用 const修飾的變量
        const val age =14
    }
}

上面程序中定義了一個(gè)頂層的const屬性遂黍,還在命名對象中定義了一個(gè) const 屬性,在伴生對象中定義了 一個(gè) const屬性俊嗽,這些 const屬性都會(huì)變成有public static final 修飾的屬性雾家。因此可用如下Java程序來調(diào)用它們。

    public static void main(String[] args) {
        System.out.println(MyClass.age);
        System.out.println(MyClassKt.MAX);
        System.out.println(Obj.name);
    }

類方法

可以肯定的是绍豁, Kotlin 的頂級函數(shù)會(huì)被轉(zhuǎn)換成類方法芯咧。
此外, Kotlin 還可以將命名對象或伴生對象中定義的方法轉(zhuǎn)換成類方法一一如果這些方法使用@ JvmStatic 修飾的話竹揍。一旦使用該注解敬飒,編譯器就既會(huì)在相應(yīng)對象的類中生成類方法,也會(huì)在對象自身中生成實(shí)例方法芬位。例如如下 Kotlin程序无拗。

//指定零個(gè)父類型的命名對象
object MyObject {
    //方法
    fun test() {
        println("test()")
    }

    @JvmStatic
    fun foo() {
        println("有@JvmStatic修飾的foo()")
    }
}

class MyClass {
    //省略名字的伴生對象
    companion object {
        //方法
        fun test() {
            println("test()")
        }

        @JvmStatic
        fun output(string: String) {
            println(string)
        }
    }
}

上面程序中定義了一個(gè)命名對象和一個(gè)伴生對象,其中命名對象中包含了兩個(gè)方法: 一個(gè)方法沒有 @JvmStatic 修飾昧碉,另一個(gè)方法有@JvmStatic 修飾:伴生對象中也包含了兩個(gè)方法: 一個(gè)方法沒有@JvmStatic 修飾英染,另 一個(gè)方法有@JvmStatic 修飾。這樣有@JvmStatic 修飾的方法既會(huì)在相應(yīng)對象的類中生成類方法晌纫,也會(huì)在對象自身中 成實(shí)例方法税迷。例如,可使用如下Java代碼來調(diào)用這些方法锹漱。

    public static void main(String[] args) {
        //通過 INSTANCE 訪問 MyObject 的單例箭养,通過單例訪問 test ()方法
        MyObject.INSTANCE.test();
        //錯(cuò)誤, test ()方法只是單例對象的實(shí)例方法
        //MyObject.test();
        //通過 INSTANCE 訪問 MyObject 的單例哥牍,通過單例訪問 foo ()方法
        MyObject.INSTANCE.foo();
        //正確毕泌, foo ()方法有自JvmStatic 修飾,因此也是 MyObject 類的類方法
        MyObject.foo();

        //通過 Companion 訪問 MyClass 的伴生對象嗅辣,通過伴生對象訪問 test ()方法
        MyClass.Companion.test();
        //錯(cuò)誤撼泛, test ()方法只是伴生對象的實(shí)例方法
        //MyClass.test();
        //通過 Companion 訪問 MyClass 的伴生對象,通過伴生對象訪問 output ()方法
        MyClass.Companion.output("sss");
        //正確澡谭, output ()方法有自 JvmStatic 修飾愿题,因此也是 MyClass 類的類方法
        MyClass.output("ccc");
    }

從上面程序中可以看出,對于命名對象中有@JvmStatic 修飾的方法蛙奖,既可通過命名對象的時(shí)STANCE (引用單例對象)來調(diào)用該方法潘酗,也可通過命名對象對應(yīng)的類來調(diào)用該方法; 對于伴生對象中有 @JvmStatic 修飾的方法,既可通過伴生對象所在類的 Companion (引用伴生對象)來調(diào)用該方法雁仲,也可通過伴生對象所在的類來調(diào)用該方法仔夺。

訪問控制符的對應(yīng)關(guān)系

Kotlin 的訪問控制符與 Java 的對應(yīng)關(guān)系如下 。

  • private 成員:依然編譯成 Java 的 private 成員攒砖。
  • protected成員: 依然編譯成 Java 的 protected成員缸兔。但需要注意的是日裙,在 Java程序中,protected成員可以被同一個(gè)包中的其他成員訪問惰蜜,但 Kotlin 不行( Kotlin 中的 protected成員只能被當(dāng)前類及其子類成員訪問)昂拂。
  • internal 成員: 會(huì)編譯成 Java 的 public 成員。但編譯成 Java 類時(shí)會(huì)通過名字修飾來避免在 Java 中被意外使用到抛猖。
  • public 成員: 依然編譯成 Java 的 public 成員政钟。

獲取 KClass

前面介紹過使用 Kotlin 來獲取 Java 的 Class 對象:反過來,有時(shí)候 Java 也需要獲取 Kotlin 的 KClass 對象樟结。為了在 Java 中獲取 Kotlin的KClass 對象养交,必須通過調(diào)用 Class<T>.kotlin 擴(kuò)展屬性的等價(jià)形式來實(shí)現(xiàn)。

    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName ("java.util.ArrayList" );
            System.out.println(clazz);
            //獲取 Class 對象的 KClass 對象
            KClass kc = JvmClassMappingKt.getKotlinClass(clazz);
            System.out.println(kc);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

使用@ JvmName 解決簽名沖突

有些時(shí)候瓢宦,在 Kotlin 中要定義兩個(gè)同名函數(shù)碎连,但JVM平臺(tái)無法區(qū)分這兩個(gè)函數(shù)。典型的情況就是類型擦除引起的問題驮履。例如如下兩個(gè)函數(shù)鱼辙。

fun List<String>.filterValid(): List<String> {
    val result = mutableListOf<String>()
    for (s in this) {
        if (s.length < 5) {
            result.add(s)
        }
    }
    return result.toList()
}


@JvmName("fileterValidint")
fun List<Int>.filterValid(): List<Int> {
    val result = mutableListOf<Int>()
    for (i in this) {
        if (i < 20) {
            result.add(i)
        }
    }
    return result.toList()
}

上面程序中定義了兩個(gè)擴(kuò)展方法,雖然程序看上去是分別為 List<String>玫镐、 List<Int>擴(kuò)展的方法倒戏,但由于編譯器編譯之后會(huì)產(chǎn)生類型擦除,因此上面兩個(gè)方法的簽名都是List.filterValid():List恐似。 所以在JVM平臺(tái)上無法區(qū)分這兩個(gè)方法杜跷。

為了讓JVM平臺(tái)能區(qū)分這兩個(gè)方法,程序中代碼使用了@JvmName("fileterValidlnt") 來標(biāo)注后一個(gè)方法矫夷,因此后 一個(gè)方法在JVM平臺(tái)上會(huì)編譯成 fileterValidlnt()方法葛闷。

    public static void main(String[] args) {
        List<String> list1 = Arrays.asList ("java","kotlin");
        System.out.println(MyClassKt.filterValid(list1));

        List<Integer> list2 = Arrays.asList (111,222);
        System.out.println(MyClassKt.fileterValidInt(list2));
    }

從上面程序中可以看出,增加@JvmName 注解之后双藕,程序就可以正常區(qū)分兩個(gè)簽名相同的方法了淑趾。

@JvmName注解也適用于屬性x和函數(shù) getX()共存。例如如下代碼:

class MyClass{
    val x: Int
        @JvmName("getX_prop")
        get() = 15

    fun getX():Int{
        return 10
    }
}

生成重載

我們知道忧陪, Kotlin為方法(或函數(shù))提供了參數(shù)默認(rèn)值來避免函數(shù)重載過多的問題扣泊。但對于這種參數(shù)有默認(rèn)值的方法(或函數(shù)),編譯器默認(rèn)只生成一個(gè)方法:默認(rèn)帶所有參數(shù)的方法 嘶摊。
為了讓編譯器能為帶參數(shù)默認(rèn)值的方法(或函數(shù))生成多個(gè)重載的方法(或函數(shù))延蟹,可考慮使用@JvmOverloads 注解 。
需要說明的是更卒,@JvmOverloads 注解也適用于構(gòu)造器等孵、靜態(tài)方法等稚照。它不適用于抽象方法蹂空,包括在接口中定義的方法 俯萌。
如下程序示范了使用@JvmOverloads 注解讓編譯器為帶參數(shù)默認(rèn)值的方法生成多個(gè)重載的方法。

@JvmOverloads
fun test(name:String ,age :Int = 24,sex:String = "男"){
    println(name)
    println(age)
    println(sex)
}

上面定義了 一個(gè)帶兩個(gè)默認(rèn)參數(shù)的函數(shù)上枕,井使用@JvmOverloads 修飾該函數(shù)咐熙,這樣編譯器將會(huì)為該函數(shù)生成如下 三個(gè)方法 。

public static final void test(java.lang.String, int, doube) 
public static final void test(java .lang.String, int)
public static final void test(java.lang.String)

checked 異常

前面已經(jīng)介紹過辨萍, Kotlin沒有 checked 異常棋恼,因此 Kotlin 也無須使用 throws 拋出異常。這樣即使 Kotlin 的方法(或函數(shù))本身可能拋出 checked 異常锈玉, Java 調(diào)用該方法(或函數(shù))時(shí)編譯器也不會(huì)檢查該異常爪飘。
例如,如下函數(shù)就存在這個(gè)問題拉背。

fun foo() {
    val fis = FileinputStream("a.txt")
}

如果我們希望 Java 調(diào)用該函數(shù)時(shí)編譯器會(huì)檢查該 IOException师崎,則可使用@Throws 注解修飾該函數(shù)。例如椅棺,將上面代碼改為如下形式犁罩。

@Throws (IOException::class)
fun foo() {
    val fis = FileinputStream("a.txt")
}

這樣上面 foo()函數(shù)就相當(dāng)于聲明了 throws IOException,因此 Java程序調(diào)用該函數(shù)時(shí)編譯 器會(huì)檢查該 checked 異常两疚,程序要么捕獲該異常床估,要么顯式聲明拋出該異常 。

泛型的型變

首先回顧一下 Kotlin 泛型型變和 Java 泛型型變的區(qū)別: Kotlin的泛型支持聲明處型變诱渤,而Java的泛型只支持使用處型變(通過通配符形式丐巫,可以支持協(xié)變和逆變兩種形式)。因此對于 Kotlin的聲明處型變勺美,必須轉(zhuǎn)換成 Java的使用處型變鞋吉。其轉(zhuǎn)換規(guī)則是:

  • 對于協(xié)變類型的泛型類 Bar<out T>,當(dāng)它作為參數(shù)出現(xiàn)時(shí)励烦, Kotlin會(huì)自動(dòng)將 Bar<Base> 類型的參數(shù)替換成 Bar<? extends Base>谓着。
  • 對于逆變類型的泛型類 Foo<in T>,當(dāng)它作為參數(shù)出現(xiàn)時(shí)坛掠, Kotlin 會(huì)自動(dòng)將 Foo<Sub> 類型的參數(shù)替換成 Foo<? super Sub>赊锚。
  • 不管是協(xié)變類型的泛型類 Bar<out T> ,還是逆變類型的泛型類 Foo<in T>屉栓, 當(dāng)它作為返回值出現(xiàn)時(shí)舷蒲, Kotlin 不會(huì)生成通配符。

例如如下 Kotlin 程序 友多。

class Box<out T>(val value: T)
open class Base 
class Sub: Base()
fun boxSub(value : Sub) : Box<Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base= box.value

上面程序中定義了一個(gè)支持聲明處協(xié)變的 Box 類牲平,接下來程序定義了兩個(gè)函數(shù),分別使用 Box<Base>作為參數(shù)類型域滥,使用 Box<Sub>作為返回值類型纵柿。編譯上面程序蜈抓,可以看到編譯器生成的兩個(gè)函數(shù)的簽名如下 :

public static final Box<Sub> boxSub(Sub)
public static final Base unboxBase(Box<? extends Base>)

從代碼可以看出,由于 Box<out T>是聲明處協(xié)變的泛型類昂儒,因此程序?qū)⑺鳛閰?shù)時(shí)沟使,編譯器自動(dòng)使用了運(yùn)行處協(xié)變來代替它。這樣 Java程序調(diào)用 unboxBase()方法時(shí)既可傳入Box<Base>作為參數(shù)渊跋,也可傳入 Box<Sub>作為參數(shù)腊嗡,只要尖括號中的類型是 Base 的子類即可。
除 Kotlin 默認(rèn)的轉(zhuǎn)換規(guī)則之外拾酝,Kotlin還可使用注解控制是否生成通配符燕少。 Kotlin提供了如下兩個(gè)注解。

  • @ JvmWildcard: 該注解可指定在編譯器默認(rèn)不生成通配符的地方強(qiáng)制生成通配符蒿囤。
  • @JvmSuppressWildcards: 該注解可指定在編譯器默認(rèn)生成通配符的地方強(qiáng)制不生成通配符棺亭。

如下程序示范了@JvmWildcard 注解的用法。

class Box<out T>(val value: T)
open class Base
class Sub: Base()
//對返回值類型強(qiáng)制生成通配符
fun boxSub(value : Sub) : Box<@JvmWildcard Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base= box.value

上面代碼使用@JvmWildcard 修飾了該函數(shù)的返回值類型 : 將會(huì)為該返回值類型生成通配符蟋软。因此镶摘,上面兩個(gè)函數(shù)編譯之后,生成兩個(gè)函數(shù)的簽名如下 :

public static final Box<? extends Sub> boxSub(Sub)
public static final Base unboxBase(Box<? extends Base>)

從上面代碼可以看出岳守,此處編譯器為返回值類型也生成了通配符(默認(rèn)是不會(huì)生成通配符的) 凄敢。

如下程序示范了@JvmSuppressWildcards 注解的用法 。

class Box<out T>(val value: T)
open class Base
class Sub: Base()
fun boxSub(value : Sub) : Box<Sub> = Box(value)
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base= box.value

上面代碼使用@JvmSuppressWildcards 修飾了該函數(shù)的形參類型: Box<Base>湿痢,因此編譯器將不會(huì)為函數(shù)的形參類型生成通配符涝缝。 因此上面兩個(gè)函數(shù)編譯之后,生成兩個(gè)函數(shù)的簽名如下:

public static final Box<Sub> boxSub(Sub)
public static final Base unboxBase(Box<Base>)

從上面代碼可以看出譬重,此處編譯器沒有為形參類型生成通配符(默認(rèn)會(huì)生成通配符) 拒逮。

@JvmSuppressWildcards 注解不僅可修飾單個(gè)泛型形參,還可修飾整個(gè)聲明(如函數(shù)或類)臀规,從 而阻止編譯器為整個(gè)類或函數(shù) 中的聲明處型變生成通配符(使用處協(xié)變)滩援。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塔嬉,隨后出現(xiàn)的幾起案子玩徊,更是在濱河造成了極大的恐慌,老刑警劉巖谨究,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恩袱,死亡現(xiàn)場離奇詭異,居然都是意外死亡胶哲,警方通過查閱死者的電腦和手機(jī)畔塔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人澈吨,你說我怎么就攤上這事把敢。” “怎么了棚辽?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冰肴。 經(jīng)常有香客問我屈藐,道長,這世上最難降的妖魔是什么熙尉? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任联逻,我火速辦了婚禮,結(jié)果婚禮上检痰,老公的妹妹穿的比我還像新娘包归。我一直安慰自己,他們只是感情好铅歼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布公壤。 她就那樣靜靜地躺著,像睡著了一般椎椰。 火紅的嫁衣襯著肌膚如雪厦幅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天慨飘,我揣著相機(jī)與錄音确憨,去河邊找鬼。 笑死瓤的,一個(gè)胖子當(dāng)著我的面吹牛休弃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播圈膏,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼塔猾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稽坤?” 一聲冷哼從身側(cè)響起桥帆,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慎皱,沒想到半個(gè)月后老虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茫多,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年祈匙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夺欲,死狀恐怖跪帝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情些阅,我是刑警寧澤伞剑,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站市埋,受9級特大地震影響黎泣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缤谎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一抒倚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坷澡,春花似錦托呕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斟赚,卻和暖如春呆抑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汁展。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工鹊碍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留食绿,地道東北人侈咕。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像器紧,于是被迫代替她去往敵國和親耀销。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容