# Kotlin教程 - 12 与 Java 互操作

Kotlin 是运行在 Java 虚拟机上的,可以和 Java 直接进行相互调用。

# 12.1 Kotlin 调用 Java

我们首先新建一个 Java 类:

Student.java 如下:

package com.doubi;

public class Student {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Student 中定义了 name 和 age 属性,并创建了 getter 和 setter。

下面在 Main.kt中调用 Student 类。

import com.doubi.Student

fun main() {
    val stu = Student()
    stu.name = "Doubi"
    stu.age = 12

    println(stu.name.length)        // 5
}
1
2
3
4
5
6
7
8
9

可以直接调用,而且 stu.name 其实是调用的 setName() 方法。

再看一下下面的代码:

import com.doubi.Student

fun main() {
    val stu = Student()
    println(stu.name.length)
}
1
2
3
4
5
6

在上面的代码中,stu.name 没有赋值,是 null,所以会报空指针异常。

所以如果 Kotlin 调用 Java,可能为空的变量,一定要使用安全操作符。

println(stu.name?.length)
1

或者在 Java 代码中添加 @Nullable 注解。

举个栗子:

package com.doubi;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class Student {
    private String name;
    private int age;

    @Nullable
    public String getName() {
        return name;
    }

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

    @NotNull
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

如果返回值可能为空的方法,可是添加 @Nullable 注解,确定不为空的函数,可以添加 @NotNull 注解。不同的平台,需要使用不同包中的@Nullable 和 @NotNull 注解。

这样调用的时候,Kotlin 中就不能使用可能为空的 name 来调用 length 了。

fun main() {
    val stu = Student()
    println(stu.name.length)    // 报错,name为可空类型,无法直接调用length属性
}
1
2
3
4

# 12.2 Java 调用Kotlin

# 1 调用类外定义的函数

在 Java 的 MainClass 类中调用 HelloKotlin。

HelloKotlin.kt 代码如下:

package com.doubi

fun sayHello() {
    println("Hello World")
}
1
2
3
4
5

如果在 Java 中调用非类中定义的代码,直接使用文件名来调用。

MainClass.java 调用代码如下:

import com.doubi.HelloKotlinKt;

public class MainClass {
    public static void main(String[] args) {
        HelloKotlinKt.sayHello();
    }
}

1
2
3
4
5
6
7
8

在上面的代码中,在 Java 中直接使用文件名来调用 Kotlin 中的函数。

如果觉得文件名不好,还可以 JvmName 注解重新定义名称,相当于起个别名。

HelloKotlin.kt 代码如下:

@file:JvmName("Hello")

package com.doubi
fun sayHello() {
    println("Hello World")
}
1
2
3
4
5
6

MainClass.java 调用代码如下:

import com.doubi.Hello;

public class MainClass {
    public static void main(String[] args) {
        Hello.sayHello();
    }
}
1
2
3
4
5
6
7

在上面的代码中,直接使用别名来调用。

# 2 调用带默认参数的函数

在 Kotlin 中定义带默认参数的函数,如下:

HelloKotlin.kt 代码:

package com.doubi
fun sayHello(name:String = "Doubi", age: Int = 12) {
    println("Hello ${name}, I'm ${age} years old")
}
1
2
3
4

此时在 Java 中调用,会发现,两个参数必须都传,没办法像 Kotlin 中只传递一个参数,因为 Java 中不支持默认参数。

这个时候需要给 Kotlin 中的函数添加 @JvmOverloads 注解,让 Kotlin 中的函数进行重载,既支持一个参数的函数,也支持两个参数的函数。

修改HelloKotlin.kt 代码:

package com.doubi

@JvmOverloads			// 添加注解,强制重载
fun sayHello(name:String = "Doubi", age: Int = 12) {
    println("Hello ${name}, I'm ${age} years old")
}
1
2
3
4
5
6

MainClass.java 调用代码如下:

import com.doubi.HelloKotlinKt;

public class MainClass {
    public static void main(String[] args) {
        HelloKotlinKt.sayHello("Niubi");		// 可以只传递一个参数。
    }
}
1
2
3
4
5
6
7

# 3 调用匿名函数

Java8 之前不支持 Lambda 表达式,那么如何调用 Kotlin 中的匿名函数呢?

举个栗子:

在 Kotlin 中定义了一个匿名函数,如下:

package com.doubi

val helloFuc = { name: String ->
    println("Hello ${name}")
}
1
2
3
4
5

那么在 Java 中如何调用 Kotlin 中的匿名函数呢?

import com.doubi.HelloKotlinKt;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

public class MainClass {
    public static void main(String[] args) {
        Function1<String, Unit> helloFuc = HelloKotlinKt.getHelloFuc();     // 获取函数
        helloFuc.invoke("Doubi");   // 调用函数
    }
}
1
2
3
4
5
6
7
8
9
10

首先使用 文件名.get匿名函数变量,然后使用 invoke 方法调用匿名函数。

在 Java 中 Kotlin 函数类型使用 FuncationN 这样的名字的接口来表示,N表示参数个数,这里的 Function1 表示有一个参数的函数,总共可以支持 Funcation0 到 Funcation22。你说你想定义 Funcation1000,你牛逼,你咋不上天呢!

# 4 @JvmStatic 注解

我们之前讲过 Kotlin 中没有静态变量和方法,但是使用伴生对象实现类似静态变量和方法的效果。

但是伴生对象中定义的属性和方法和 Java 中的静态属性和方法是由区别的,虽然可以通过类名来访问,但是并不是真正的静态属性和方法。

我们可以在伴生对象中的属性和方法前添加@JvmStatic 注解,这个注解有什么作用呢?

这个注解用来告诉编译器将属性或函数编译为真正的 JVM 静态成员,和 Java 中的一样。

举个栗子:

package com.doubi

class Student {
    // 定义伴生对象
    companion object {
        // 定义属性
        @JvmStatic
        var stuCount: Int = 0
        // 定义方法
        @JvmStatic
        fun getCount():Int {        // 在伴生对象中定义的方法为静态方法
            return stuCount
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

我们在伴生对象中的属性和方法前面添加了@JvmStatic 注解,这样属性和方法会被编译成 JVM 静态成员,那么在 Java 可以直接使用 Student类名来调用伴生对象中的属性和方法。

import com.doubi.Student;

public class MainClass {
    public static void main(String[] args) {
        System.out.println(Student.getStuCount());      // 通过类名调用
        Student.getCount();     // 调用方法
    }
}
1
2
3
4
5
6
7
8

如果不添加@JvmStatic 注解,我们在 Java 代码中调用 stuCount 属性,需要使用 Student.Companion.getStuCount()来调用。

import com.doubi.Student;

public class MainClass {
    public static void main(String[] args) {
        System.out.println(Student.Companion.getStuCount());      // 需要通过Companion对象来调用
        Student.Companion.getCount();    
    }
}
1
2
3
4
5
6
7
8

# 5 @JvmField

在上面虽然使用了 @JvmStatic 注解后,可以使用类名来调用属性了,但是还是要通过 getter 或 setter 来调用,能否像 Java 中一样,直接通过类名来调用静态属性呢?

这里需要使用 @JvmField 注解。

package com.doubi

class Student {
    // 定义伴生对象
    companion object {
        // 定义属性
        @JvmField
        var stuCount: Int = 0
        // 定义方法
        @JvmStatic
        fun getCount():Int {        // 在伴生对象中定义的方法为静态方法
            return stuCount
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在上面使用了 @JvmField 修饰了伴生对象中的属性。

那么在 Java 中的调用可以直接通过名称来调用。

import com.doubi.Student;

public class MainClass {
    public static void main(String[] args) {
        System.out.println(Student.stuCount);      // 直接通过名称来调用
    }
}
1
2
3
4
5
6
7

这是因为 @JvmField 注解将属性变成了 public 了,所以可以直接调用。

@JvmField 注解用在伴生对象的属性上,那么属性就是 public static 的,@JvmField 注解还可以用在类的属性上,那么属性就是public 的。

package com.doubi

class Student {
    @JvmField
    var name = "Doubi"
  
    @JvmField
    val age = 12
}
1
2
3
4
5
6
7
8
9

那么在 Java 中,可以直接通过 对象.属性名 直接调用,如下:

import com.doubi.Student;

public class MainClass {
    public static void main(String[] args) {
        Student stu = new Student();
        System.out.println(stu.name);      // Doubi
    }
}
1
2
3
4
5
6
7
8