kotlin 与java 互操作

822

简介

大多数情况下,你不需要关注这个问题。但是,如果你的代码中包含了部分Java代码,了解这些可能帮你解决一些棘手的问题,同时让你设计的Api更加可靠

互操作性与可空性

Java世界里所有对象都可能是null,当一个kotlin函数返回string类型值,你不能想当然地认为它的返回值就能符合kotlin关于空值的规定

kotlin

fun main() {
    val my = MyClass()
    val value =  my.getCanNullValue()
    println(value?.capitalize()) 
}

java

public class MyClass {
    public String value;
    
    public String getCanNullValue(){
        return value;
    }
}

类型映射

代码运行时,所有的映射类型都会重新映射回对应的java类型

fun main() {
    val my = MyClass()
    my.value = "a123"
    val value =  my.getCanNullValue()
    println(value.javaClass) 
}

结果为:class java.lang.String

属性访问

不需要调用相关setter方法,你可以使用赋值语法来设置一个java字段值了

val my = MyClass()
my.value = "a123"

@JvmName

这个注解可以改变字节码中生成的类名或方法名称,如果作用在顶级作用域(文件中),则会改变生成对应Java类的名称。如果作用在方法上,则会改变生成对应Java方法的名称。

kotlin

@file:JvmName("FooKt")
@JvmName("foo1")
fun foo() {
    println("Hello, Jvm...")
}

java

// 相当于下面的Java代码
public final class FooKt {
   public static final void foo1() {
      String var0 = "Hello, Jvm...";
      System.out.println(var0);
   }
}

第一个注解@file:JvmName("FooKt")的作用是使生成的类名变为FooKt,第二个注解的作用是使生成的方法名称变为foo1

@JvmField

Kotlin编译器默认会将类中声明的成员变量编译成私有变量,Java语言要访问该变量必须通过其生成的getter方法。而使用上面的注解可以向Java暴露该变量,即使其访问变为公开(修饰符变为public)。

Kotlin

class JavaToKotlin {
    @JvmField
    val info = "Hello"
}

@JvmOverloads

由于Kotlin语言支持方法参数默认值,而实现类似功能Java需要使用方法重载来实现,这个注解就是为解决这个问题而生的,添加这个注解会自动生成重载方法

Kotlin

@JvmOverloads
fun prinltInfo(name: String, age: Int = 1) {
    println("$name $age")
}

java

 public static void main(String[] args) {
        MyKotlin.prinltInfo("arrom");
        MyKotlin.prinltInfo("arrom", 20);
    }

@JvmStatic

@JvmStatic注解的作用类似于@JvmField,可以直接调用伴生对象里的函数

class JavaToKotlin {
    @JvmField
    val info = "Hello"

    companion object {
        @JvmField
        val max: Int = 200

        @JvmStatic
        fun loadConfig(): String {
            return "loading config"
        }
    }
}

@Throws

由于Kotlin语言不支持CE(Checked Exception),所谓CE,即方法可能抛出的异常是已知的。Java语言通过throws关键字在方法上声明CE。为了兼容这种写法,Kotlin语言新增了@Throws注解,该注解的接收一个可变参数,参数类型是多个异常的KClass实例。Kotlin编译器通过读取注解参数,在生成的字节码中自动添加CE声明。

Kotlin

@Throws(IllegalArgumentException::class)
fun div(x: Int, y: Int): Float {
    return x.toFloat() / y
}

Java

// 生成的代码相当于下面这段Java代码
public static final float div(int x, int y) throws IllegalArgumentException {
      return (float)x / (float)y;
}

添加了@Throws(IllegalArgumentException::class)注解后,在生成的方法签名上自动添加了可能抛出的异常声明(throws IllegalArgumentException),即CE。

@Synchronized

用于产生同步方法。Kotlin语言不支持synchronized关键字,处理类似Java语言的并发问题,Kotlin语言建议使用同步方法进行处理

Kotlin

@Synchronized
fun start() {
    println("Start do something...")
}

java

// 生成的代码相当于下面这段Java代码
public static final synchronized void start() {
  String var0 = "Start do something...";
  System.out.println(var0);
}

函数类型操作

Java中没有函数类型,所以,在Java里,kotlin函数类型使用FunctionN这样的名字的接口来表示,N代表入参的个数,一共有24个这样的接口,从Function0到Function23,每个接口都包含一个invoke函数,调用匿名函数需要调用invoke

kotlin:

val funcp:(String) -> String = {
  it.capitalize()
}

java:

Function1<String, String> funcp = ArromKt.getFuncp();
funcp.invoke("arrom");