26.Kotlin与Java互操作

Kotlin与Java互操作

在Kotlin中操作Java

示例代码:
Person类

package com.leofight.kotlin10;

public class Person {

    private String name;

    private boolean married;

    private int age;

    public String getName() {
        return name;
    }

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

    public boolean isMarried() {
        return married;
    }

    public void setMarried(boolean married) {
        this.married = married;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


fun main(args: Array<String>) {
    val list = ArrayList<String>()

    list.add("hello")
    list.add("world")
    list.add("hello world")

    for (item in list) {
        println(item)
    }

    println("------")

    for (i in 0 until list.size) {
        println(list[i])
    }

    println("-------")

    val person = Person()

    person.age = 20
    person.isMarried = false
    person.name = "zhangsan"

    println(person.age)
    println(person.isMarried)
    println(person.name)

    println("------")

   
}

在Java中,所有引用都可能为null,然而在Kotlin中,对null是有着严格的检查与限制的,这就使得某个来自于Java的引用在Kotlin中变得不再适合;基于这个原因,在Kotlin中,将来自于Java的声明类型称为平台类型(Platform Types)。

对于这种类型(平台类型)来说,Kotlin的null检查就会得到一定的缓和,变得不再那么严格了。这样就使得空安全的语义要求变的与Java一致。

当我们调用平台类型引用的方法时,Kotlin就不会在编译期间施加空安全的检查,使得编译可以正常通过;但是在运行期间则 可能抛出异常,因为平台类型引用值就可能为null。


fun main(args: Array<String>) {
    val list2 = ArrayList<String>()

    //list2.add("hello")

    val size = list2.size
    val item = list2[0]

    println(size)
    println(item)

    val s: String? = item //允许,总是可以的
    val s2: String = item //允许,不过可能会在运行期间失败
}

如果我们使用了不可空类型,编译器会在赋值时生成一个断言。这会防止Kotlin的不可空变量持有null值;同样,这一点也适用于Kotlin方法参数传递,我们在将一个平台类型值传递给方法的一个不可空参数时,也会生成一个断言。

总体来说,Kotlin会竭尽所能防止null的赋值蔓延到程序的其他地方,而是在发生问题之处就立刻通过断言来解决。

数组(Array)

Kotlin中数组是不变的(相对于协变与逆变来说),这一点与Java存在明显的不同。这意味着,我们无法将一个Array<String>赋给Array<Any>,这样就可以防止可能出现的运行期异常。

Kotlin提供了原生类型数组来避免自动装箱与拆箱带来的成本:IntArray、DoubleArray、CharArray...

示例代码:


public class MyArray {

    public void myArrayMethod(int[] args) {

    }
}
fun main(args: Array<String>) {
    val myArray = MyArray()

    val intArray = intArrayOf(1, 2, 3, 4)

    myArray.myArrayMethod(intArray)

    println("---------")

    //当编译为JVM字节码时,编译器会优化对于数组的访问,使之不会产出额外的成本

    val array = arrayOf(1, 2, 3, 4)

    array[0] = array[0] * 2 //并不会调用set或者get方法

    for (x in array) {
        println(x)
    }


}

可变参数

public class MyVarargs {

    public void myMthod(String... strings) {

    }
}
fun main(args: Array<String>) {
    val myVarargs = MyVarargs()

    val stringArrays = arrayOf("hello", "world", "hello world")

    myVarargs.myMthod(*stringArrays) //spread operation *
}

受检异常
在 Kotlin 中,所有异常都是非受检的,这意味着编译器不会强迫你捕获其中的任何一个。 因此,当你调用一个声明受检异常的 Java 方法时,Kotlin 不会强迫你做任何事情:

public class MyException {

    public void myMethod() throws IOException {
        throw new IOException("I/O异常");
    }
}
fun main(args: Array<String>) {
    /* val myException = MyException();
     myException.myMethod();*/

    println("--------")

    //要取得对象的 Java 类
    val clazz = MyException()::class.java
    println(clazz)

    println("---------")

    println(MyException().javaClass)
}

Java操作Kotlin

属性(properties)

一个Kotlin属性会被编译为3部分Java元素
1.一个getter方法
2.一个setter方法
3.一个私有的字段(field),其名字与Kotlin的属性名一样

如果Kotlin属性名以is开头,那么命名约定会发生一些变化。
1.getter方法名与属性名一样
2.setter方法名则是将is替换为set

这种规则适用于任何类型,而不单单是Boolean类型。

例如,var firstName: String 编译成以下 Java 声明:

private String firstName;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

示例

class MyClass

fun test() {
    println("hello world")
}

var str: String = "hello"
public class HelloJava2 {
    public static void main(String[] args) {
        //我们无法通过new关键字来创建Kotlin编译器自动生成的以Kt结尾的类的实例。
        //因为在生成的字节码中没有不带参数的构造方法。
        //HelloKotlin2Kt helloKotlin2Kt = new HelloKotlin2Kt();

        MyClass myClass = new MyClass();

        HelloKotlin2Kt.setStr("welcome");
        System.out.println(HelloKotlin2Kt.getStr());

        HelloKotlin2Kt.test();
    }
}

输出

welcome
hello world

包级函数
在 org.foo.bar 包内的 example.kt 文件中声明的所有的函数和属性,包括扩展函数, 都编译成一个名为 org.foo.bar.ExampleKt 的 Java 类的静态方法。

// example.kt
package demo

class Foo

fun bar() { …… }
// Java
new demo.Foo();
demo.ExampleKt.bar();

可以使用 @JvmName 注解修改生成的 Java 类的类名:

@file:JvmName("DemoUtils")

package demo

class Foo

fun bar() { ... }
// Java
new demo.Foo();
demo.DemoUtils.bar();

如果多个文件中生成了相同的 Java 类名(包名相同并且类名相同或者有相同的 @JvmName 注解)通常是错误的。然而,编译器能够生成一个单一的 Java 外观类,它具有指定的名称且包含来自所有文件中具有该名称的所有声明。 要启用生成这样的外观,请在所有相关文件中使用 @JvmMultifileClass 注解。

// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun foo() { ... }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun bar() { ... }
// Java
demo.Utils.foo();
demo.Utils.bar();

实例字段

如果需要在 Java 中将 Kotlin 属性作为字段暴露,那就需要使用 @JvmField 注解对其标注。 该字段将具有与底层属性相同的可见性。如果一个属性有幕后字段(backing field)、非私有、没有 open /override 或者 const 修饰符并且不是被委托的属性,那么你可以用 @JvmField 注解该属性。

class C(id: String) {
    @JvmField val ID = id
}
// Java
class JavaClient {
    public String getID(C c) {
        return c.ID;
    }
}

延迟初始化的属性(在Java中)也会暴露为字段。 该字段的可见性与 lateinit 属性的 setter 相同。

静态字段

在命名对象或伴生对象中声明的 Kotlin 属性会在该命名对象或包含伴生对象的类中具有静态幕后字段。

通常这些字段是私有的,但可以通过以下方式之一暴露出来:

  • @JvmField 注解;
  • lateinit 修饰符;
  • const 修饰符。
    使用 @JvmField 标注这样的属性使其成为与属性本身具有相同可见性的静态字段。
class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
    }
}
// Java
Key.COMPARATOR.compare(key1, key2);
// Key 类中的 public static final 字段

在命名对象或者伴生对象中的一个延迟初始化的属性具有与属性 setter 相同可见性的静态幕后字段。

object Singleton {
    lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// 在 Singleton 类中的 public static 非-final 字段

用 const 标注的(在类中以及在顶层的)属性在 Java 中会成为静态字段:

// 文件 example.kt

object Obj {
    const val CONST = 1
}

class C {
    companion object {
        const val VERSION = 9
    }
}

const val MAX = 239

在Java中

int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;

静态方法

如上所述,Kotlin 将包级函数表示为静态方法。 Kotlin 还可以为命名对象或伴生对象中定义的函数生成静态方法,如果你将这些函数标注为 @JvmStatic 的话。 如果你使用该注解,编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。 例如:

class C {
    companion object {
        @JvmStatic fun foo() {}
        fun bar() {}
    }
}

现在,foo() 在 Java 中是静态的,而 bar() 不是:

C.foo(); // 没问题
C.bar(); // 错误:不是一个静态方法
C.Companion.foo(); // 保留实例方法
C.Companion.bar(); // 唯一的工作方式

对于命名对象也同样:

object Obj {
    @JvmStatic fun foo() {}
    fun bar() {}
}

在Java中

Obj.foo(); // 没问题
Obj.bar(); // 错误
Obj.INSTANCE.bar(); // 没问题,通过单例实例调用
Obj.INSTANCE.foo(); // 也没问题

@JvmStatic 注解也可以应用于对象或伴生对象的属性, 使其 getter 和 setter 方法在该对象或包含该伴生对象的类中是静态成员。

用 @JvmName 解决签名冲突

有时我们想让一个 Kotlin 中的命名函数在字节码中有另外一个 JVM 名称。 最突出的例子是由于类型擦除引发的:

fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>

这两个函数不能同时定义,因为它们的 JVM 签名是一样的:filterValid(Ljava/util/List;)Ljava/util/List;。 如果我们真的希望它们在 Kotlin 中用相同名称,我们需要用 @JvmName 去标注其中的一个(或两个),并指定不同的名称作为参数:

fun List<String>.filterValid(): List<String>

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

在 Kotlin 中它们可以用相同的名称 filterValid 来访问,而在 Java 中,它们分别是 filterValid 和 filterValidInt。

同样的技巧也适用于属性 x 和函数 getX() 共存:

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

fun getX() = 10

如需在没有显式实现 getter 与 setter 的情况下更改属性生成的访问器方法的名称,可以使用 @get:JvmName 与 @set:JvmName:

@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23

生成重载

通常,如果你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见,如果希望向 Java 调用者暴露多个重载,可以使用 @JvmOverloads 注解。

该注解也适用于构造函数、静态方法等。它不能用于抽象方法,包括在接口中定义的方法。

class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
    @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") { …… }
}

对于每一个有默认值的参数,都会生成一个额外的重载,这个重载会把这个参数和它右边的所有参数都移除掉。在上例中,会生成以下代码 :

// 构造函数:
Foo(int x, double y)
Foo(int x)

// 方法
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }

请注意,如次构造函数中所述,如果一个类的所有构造函数参数都有默认值,那么会为其生成一个公有的无参构造函数。这就算没有 @JvmOverloads 注解也有效。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容