如何在 Java 和 Kotlin 之间进行互操作

Posted Android编程精选

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在 Java 和 Kotlin 之间进行互操作相关的知识,希望对你有一定的参考价值。

点击右侧关注,了解黑客的世界!

点击右侧关注,掌握进阶之路!

点击右侧关注,探讨技术话题!


作者丨四爷
来源丨android开发之旅(AndroidDevTour)

01

前言


目前kotlin是谷歌首推的开发Android的语言,但由于历史原因,我们绝大部分项目依旧还是以Java为主的,也就是说存在Java和Kotlin两种语言同时开发的情况。


有人会说把老项目全部翻译成Kotlin,的确可以怎么做,但是成本还是挺大的。我们只能一点一点慢慢的向kotlin语言迁移。


那么在迁移的过程中就避免不了Java和Kotlin相互调用的情况。即Kotlin调用Java或者Java调用Kotlin。下面我们就来具体看下两者之间相互操作的一些解决方案。



02

kotlin调用java



 1. 可空性(Nullability)

Java默认有数值可空性而kotlin没有,所以在调用Java的方法的时候不知道会不会收到空值。

所以我们在Kotlin中调用Java的时候需要添加 ?或者 !来告诉Kotlin有可能出现空值。


比如这里有一个Java方法,接受一组字符串后返回一组做字符串:


  
    
    
  
public Set<String> toSet(Collection<String> elements){ //TODO }


那么Kotlin在调用的时候是不能确定输入和输出是否可为空的。就需要使用?或者 !来辅助判断。

为了方便Kotlin调用,我们通常使用 @NotNull 注解来标识Java代码的非原始参数、字段、返回值。


  
    
    
  
@NotNull Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements){ //TODO }

这个Kotlin在调用的时候就明确知道不能为空,这里我们使用的是jetBrain的 @NotNull注解,当然还有其他选择,如下图:



这里还是推荐使用JetBrain或者Android的注解。


2. 前缀属性:(getter、setter)


如果是使用Java bean,那么我们在Kotlin中调用就没有什么问题。


如果你的空参数方法是以get开头的,那么Kotlin就知道这是getter,就可以通过属性名来访问它。

相同的如果是由set开头的单一参数方法,那么Kotlin就知道这是setter,就通过属性名直接赋值。

当然is的工作原理也是和它们类似的。 


我们定义一个Java bean:


  
    
    
  
class User {
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; } }


Kotlin中访问


  
    
    
  
val user = User() user.name = "四爷" //赋值 val age = user.age //获取age字段值


3. 关键字(keywords)


kotlin中有很多系统定义的关键字如 fun is in objects、typeof、val、var、when、typealias等。


这些关键字在java是可以被使用的,但是在kotlin却是不行的。


函数或者参数使用了这些关键字,那么kotlin在调用的时候会出现一些问题,比如Java中定义了一个方法名叫 is 的方法。那么在Kotlin中直接调用就会报错。


那么最简单的方法就是重命名Java方法,但如果调用的是三方库的方法,就很难去重命名了。

所以我们另一种解决方式是在Kotlin调用java方法的时候加上 `` 反引号来使用。

  
    
    
  
Utils.`is`()

但是我们如果能重命名还是重命名,以防止代码出现太多的符号。 


4. 避免在任何扩展方法和扩展属性上使用Any


如何在 Java 和 Kotlin 之间进行互操作


5. 运算符重载(operator Overloading)


在Java不存在运算符重载,而kotlin有。比如:

  
    
    
  
a+b => a.plus(b)

在kotlin中将运算符 + 翻译为了方法 plus。


如果在Java中使用了同样的方法名称,比如 加(plus)、 减(minus)或者其他运算符名称,那么请务必确保他们与运算符兼容,避免意外调用他们。
  

03

Java调用Kotlin

1. JvmName & JvmMultifileClass

当我们在迁移的时候会将Java的工具类翻译为Kotlin拓展函数或者顶层函数。但是这样处理之后,在Java文件中是无法直接调用的,此时我们需要加注解 @file:JvmName(“文件名称”):


Ext.kt文件

  
    
    
  
@file:JvmName("ExtUtils") package com.demo.javaAndKotlin
fun a(): String { ... }
fun b(): String { ... }


这里我们将名称命名为ExtUtils。此外,我们可能还有其他的顶层函数或者扩展函数。按照上面这种方式我们也可以指定一个其他的名称,但是如果我们也想使用ExtUtils这个名称的时候会报错:

  
    
    
  
Duplicate JVM class name

此时我们需要在不同的文件中加入新的注解 @file:JvmMultifileClass 。意思是将所有的文件合并到一个新的名称为ExtUtils文件中。


ExtOther.kt文件


  
    
    
  
@file:JvmMultifileClass @file:JvmName("ExtUtils") package com.demo.javaAndKotlin
fun c(str: Any): String { ... }


我们在Ext.kt文件中也加入@file:JvmMultifileClass注解,我们就可以在Java文件中直接使用ExtUtils来调用 a()、b()、c()方法了。


2. JvmField 


在 kotlin中我们使用的数据类即 data class 是不需要指定getter和setter的,可以直接通过字段名来访问它们。但是如果是在Java文件中调用data class依旧是需要使用getter和setter方法进行调用的。这里我们是可以修改他们的,那就是使用 @JvmField 注解,通过注解,可以直接将字段暴露出去进行访问。


  
    
    
  
data class Person( @JvmField var name: String, @JvmField var age: Int ) //java中调用 Person person = new Person("",1); person.name = ""; person.age = 10;

但是也有例外就是lateinit修饰的字段会自动暴露,无需指定@JvmField注解。还有const修饰的字段也是一样会自动暴露。


另外,如果我们想在Java中调用setName的时候修改这个属性名称不叫setName,这里我们需要使用@set:JvmName 注解。同理修改getName使用@get:JvmName 。需要注意的是,指定了@set:JvmName或者@get:JvmName注解后不需要在指定@JvmField了。


  
    
    
  
data class Person( @set:JvmName("changeName") var name: String, @JvmField var age: Int, @get:JvmName("likesPink") var likesPink: Boolean ){ lateinit var address:String }


3. JvmStatic


当我们将Java文件的静态方法迁移到Kotlin中时,我们会将其放在 companion object中,但是这样处理之后在Java文件中无法直接调用,得通过companion对象实例方法来调用。


  
    
    
  
class MyService { internal fun doWork() { ... }
companion object { fun schedule(context: Context) { ... } } }
//在Java中调用 MyService.Companion.schedule(this);


幸运的是Kotlin提供了 @JvmStatic 注解。他会让Kotlin在编译器完成类封装后生成一个静态方法。


  
    
    
  
class MyService { internal fun doWork() { ... }
companion object { @JvmStatic     fun schedule(context: Context) { ... } } } //在Java中调用 MyService.schedule(this);


4. JvmOverloads


在Kotlin中我们可以给函数的参数设置默认值,即默认参数。但是这个功能在Java中是没有的。如果不做任何处理,那么在Java中调用函数的时候,就必须每个参数都要传入。那么我们设置的默认参数就没有任何意义了。


所以,Kotlin给我们提供了 @JvmOverloads注解,使用这个注解后,会让Kotlin编译器按照从左向右的顺序依次为每一个可选参数生成重载。 


  
    
    
  
@JvmOverloads fun Bitmap.resize(width: Int, height: Int = 200) {     ... } //java调用 ExtUtils.resize(bitmap,100);
这里我们在Kotlin中很容易就理解了Bitmap.resize方法的含义,但是ExtUtils.resize这样调用的时候,方法名不够明确。所以我们可以使用@JvmName注解来指定名称。


@JvmName("resizeBitmap")@JvmOverloadsfun Bitmap.resize(width: Int, height: Int = 200) {    ...}//java调用ExtUtils.resizeBitmap(bitmap,100);


 推荐↓↓↓ 

以上是关于如何在 Java 和 Kotlin 之间进行互操作的主要内容,如果未能解决你的问题,请参考以下文章

KotlinKotlin 与 Java 互操作 ③ ( Kotlin 中处理 Java 异常 | Java 中处理 Kotlin 异常 | @Throws 注解处理异常 | 函数类型互相操作 )

Kotlin 初学者Java和Kotlin互操作

Kotlin 初学者Java和Kotlin互操作

Kotlin 初学者Java和Kotlin互操作

深入kotlin - 与Java互操作:kotlin调用java

深入kotlin - 与Java互操作:kotlin调用java