首页 > 编程语言 >在Java代码中更优雅地调用Kotlin

在Java代码中更优雅地调用Kotlin

时间:2023-04-21 11:01:49浏览次数:52  
标签:Java Kotlin List send 优雅 fun Event

在这里插入图片描述-
Kotlin与Java良好的互操作性是其能够快速普及的原因之一。从Java虽然可以访问Kotlin,但是通过下面这些技巧可以让对Kotlin的访问变得更加友好和地道

@JvmStatic


Kotlin中可以使用object class创建单例

object Analytics {
  fun init() {...}
  fun send(event: Event) {...}
  fun close() {...}
}

Kotlin侧可以像Java的静态方法一样访问其方法

Analytics.send(Event("custom_event"))

但Java侧会生成INSTANCE单例对象,使用起来有些啰嗦

Analytics.INSTANCE.send(new Event("custom_event"));

如何在Java中也能像访问静态方法一样访问object class?

使用@JvmStatic注解

object Analytics {
  @JvmStatic fun init() {...}
  @JvmStatic fun send(event: Event) {...}
  @JvmStatic fun close() {...}
}
...
// Java call-site
Analytics.send(new Event("custom_event"));

除了方法以外,我们可以对public的属性添加@JvmStatic实现静态访问,并有选择的屏蔽setget方法

@JvmStatic var isInited: Boolean = false
private set

    -

@JvmOverloads


data class Event(val name: String, val context: Map<String, Any> = 
  emptyMap())

kotlin的默认参数让方法拥有更多签名,实现方法重载的效果,可以使用两种构造函数构造Event:

Event("")
Event("", map)

Java不支持默认参数,但是使用@JvmOverloads,可以在Java侧生成多个重载方法:

data class Event @JvmOverloads constructor(val name: String, val context: Map<String, Any> = emptyMap())

    -

@Throws


Kotlin中没有Checked Exception

interface Plugin {
  fun init()
  /** @throws IOException if sending failed */
  fun send(event: Event)
  fun close()
}

如上,send方法虽然有可能抛出IOException,但是编译器不强制需要try...catch...

// object Analytics
@JvmStatic fun send(event: Event) {
  log("<Internal Send event>")

  plugins.forEach { 
    try {
      it.send(event)
    } catch (e: IOException) {
      log("WARN: ${it.javaClass.simpleName} fired IOE")
    } 
  }
}

由于send在运行时有可能抛出IOException,Java中实现Plugin接口时,为了安全性考虑可能会为send添加throws声明,此时编译器会给出错误

Overridden method does not throw IOException

使用@Throw可以在Java中让编译器知道这个方法会抛出CE

interface Plugin {
  fun init()
  /** @throws IOException if sending failed */
  @Throws(IOException::class)
  fun send(event: Event)
  fun close()
}

    -

@JvmName


属性

Kotlin的属性在Java侧会翻译成配套的get/set方法,而且遵循Java的命名习惯,使用起来非常友好:

  • name : String -> getName()/setName()
  • isName: boolean -> isName()/setName()

但是像hasName这样的属性,就没那么智能了,会翻译成getHasName()

如何指定成hasName()呢?使用@JvmName

val hasName @JvmName("hasName") get() = mNames.isNotEmpty()

也可以针对get/set选择性的指定

@get:JvmName("hasName") val hasName get() = 
	mNames.isNotEmpty()

方法

如果我们定义如下两个扩展方法:

fun List<Int>.printReversedSum() {
  println(this.foldRight(0) { it, acc -> it + acc })
}

fun List<String>.printReversedSum() {
  println(this.foldRight(StringBuilder()) {
    it, acc -> acc.append(it)
  })
}

由于泛型擦除,经过字节码反编译Java后的签名无法区分:

public static final void printReversedSum(@NotNull List $receiver)

此时可以借助于@JvmName

fun List<Int>.printReversedSum() {
  println(this.foldRight(0) { it, acc -> it + acc })
}
@JvmName("printReversedConcatenation")
fun List<String>.printReversedSum() {
  println(this.foldRight(StringBuilder()) {
    it, acc -> acc.append(it)
  })
}

文件

Kotlin支持top-level的方法或属性,但是Java的方法和属性只能存在与Class中,所以Java侧会为顶级方法/属性作为静态成员定义在一个文件名+kt后缀的Class中。

使用@JvmName可以对这个Class的名字进行指定:

//reverser.kt
package util
fun String.reverse() = StringBuilder(this).reverse().toString()

如上,Java中使用ReverserKt访问静态方法

//reverser.kt
@file:JvmName("ReverserUtils")
package util
fun String.reverse() = StringBuilder(this).reverse().toString()

如上,Java中使用ReverserUtils访问静态方法,更加友好

@JvmMultifileClass


当对多个文件使用@JvmName并指定同一个名字时,编译器会提示重复定义,此时可以@JvmMultifileClass将Kt侧多个文件在Java中合成一个,避免编译器出错:

//Collections.kt
@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("CollectionsKt")

package kotlin.collections

-----
//Iterables.kt
@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("CollectionsKt")
package kotlin.collections

    -

@JvmField


@JvmField可以消除backing fieldget/set,让其在java侧向var一样被访问:

class KotlinJvmSample {
    @JvmField
    val example = "Hello!"
}

companion object 中的公共函数必须用使用 @JvmStatic 注解才能暴露为静态方法。-
如果没有这个注解,这些函数仅可用作静态 Companion 字段上的实例方法。

class Sample {
    companion object {
        @JvmField val MAX_LIMIT = 20
    }
}

相当于

public class Sample {
    public static final int MAX_LIMIT = 20;
}

    -

@JvmWildcard

这个注解主要用于处理泛型参数,这涉及**型变(逆变与协变)**的知识点。-
Kotlin支持型变,List<Dog>自动可以用在所有List<Animal>的地方,但Java不支持型变,需要使用?通配符作为参数:<?extends ...> 或者 <? super …>

@JvmWildcard 用来帮助在Java侧生成通配符。

在返回值出现泛型类型时:

fun getPlugins(): List<Plugin>

在Java侧无法生成通配符

public final List<Plugin> getPlugins()

添加@JvmWildcard

fun getPlugins(): List<@JvmWildcard Plugin>

可以生成通配符

public final List<? extends Plugin> getPlugins()

@JvmSuppressWildcards

在入参有泛型类型时

fun addPlugins(plugs: List<Plugin>)

上面代码在Java中会自动添加通配符

public final void addPlugins(@NotNull List<? extends Plugin> plugs)

@JvmSuppressWildcards@JvmWildcard 相反,是抑制通配符的生成。

添加的位置不同作用范围不同:

// Suppress only for this param.
fun addPlugins(plugs: List<@JvmSuppressWildcards Plugin>)

// Suppress for the whole method.
@JvmSuppressWildcards fun addPlugins(plugs: List<Plugin>)

// Or even for the whole class.
@JvmSuppressWildcards
object Analytics {
  fun addPlugins(plugs: List<Plugin>)
}

    -

@JvmSynthetic


有时候有些Kotlin方法不想暴露给Java,此时可以使用@JvmSynthetic,此外还有一个trick的方法就是使用@JvmName("")定义一个Java中非法的名字,这样也可以隐藏Kotlin的方法。

@ JvmDefault


Kotlin1.3新增的操作符,可以让Kotlin的interface中的default方法出现在Java中,-
详细可参考:Java与Kotlin混合编程之@JvmDefault

Java访问Kotlin的其他注意事项


Builder

Kotlin中我们有apply等作用域函数,一般不需要使用Builder,但是为了兼容Java,我们可以定义Builder如下:

class Builder {
  private lateinit var baseUrl: String
  private var client: Client = Client()

  fun baseUrl(baseUrl: String) = this.also { it.baseUrl = baseUrl }

  fun client(client: Client) = this.also { it.client = client }

  fun build() = Retrofit(baseUrl, client)
}

Kotlin写builder也如此方便:对于必需的字段使用lateinit,通过also{ }还可以避免return this

Internal

Kotlin的Internal在Java中仍然是可见的,需要特别注意,如果不想暴露可以添加@JvmSynthetic

Null-checks

Kotlin的方法参数或者返回值,在Java中会根据其可空性添加@Nonnull@Nullable ,有助于IDE的提醒。

Inline函数

Inline方法在Java中可以正常访问,但是有reified参数的inline函数无法访问,因为Java无法处理reified

Unit

Kotlin侧的Lambda返回Unit时,Java必须强制处理一下return,如下

ReverserUtils.forEachReversed(list, it -> {
  System.out.println(it);
  return null;
});

Typealiases

Java无法访问Typealiases定义的类型,不过问题不大。

跳转到 Cubox 查看

标签:Java,Kotlin,List,send,优雅,fun,Event
From: https://www.cnblogs.com/cps666/p/17339572.html

相关文章

  • 对kotlin友好的现代 JSON 库 moshi 基本使用和实战
    对kotlin友好的现代JSON库moshi基本使用和实战blog.csdn.net成就一亿技术人!前言上一篇博客我们聊了下gson在处理kotlindataclass时的一些坑,感兴趣的可以了解一下:gson反序列化成dataclass时的坑总结一下有一下两点属性声明时值不能为null,结果反序列化后值为null,跟预......
  • 推荐使用 Kotlin 关键字 Reified
    原文地址zhuanlan.zhihu.com推荐使用Kotlin关键字Reified残枫cps​目录收起1.不再需要传参数clazzreified方式2.不安全的转换reified方式3.不同的返回类型函数重载reified方式原文地址www.jianshu.comreified:使抽象的东西更加具体或真实,非常推荐Android开......
  • JAVA中的规则
    是看的尚硅谷的教材粘贴过来的,只是自己记录。如何看懂UML类图?没有好记性就多看几遍UML(UnifiedModelingLanguage,统一建模语言),用来描述软件模型和架的图形化语言。常用的UML工具软件有PowerDesinger、Rose和EnterpriseArchitect.UML工具软件不仅可以绘制软件开发中所......
  • 检查java的class文件版本
     packagetest;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.IOException;importjava.util.HashMap;importjava.util.Map;publicclassCheckClassVersion{publicstaticvoidmain(String[]......
  • java堆栈方法区
    原文地址zhuanlan.zhihu.comjava堆栈方法区残枫cps​目录收起栈区堆区方法区栈区栈区描述的是方法执行的内存模型。每个方法在执行时都会创建一个栈帧(存放存储局部变量、操作数栈、动态链接、方法出口等)JVM为每个线程创建一个栈,栈属于线程私有,不能实现线程间的共享!用......
  • JavaScript 测试及效验工具
    JavaScript是一款强大的广泛运用于现代Web站点及应用的脚本语言。作为一个技艺精湛的Web开发者,掌握JavaScript可以增强用户的使用体验,提供交互及富客户端等功能。尽管JavaScript的语法非常简单,但对于写程序而言仍然是困难重重,就是因为它的运行环境:基于Web浏览器。以下您可以看......
  • Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图
    一、总体原则列出执行顺序的原则(这里本人出了简化,比较明了。可能有漏的,请帮忙补充,但应付该实例足以):  ==父类先于子类;  ==静态先于非静态;  ==变量和块先于构造方法;  ==变量声明先于执行(变量赋值、块执行);(这一点是根据数据在内存中是如何存储的得出的,基本类型、对象、......
  • java调用python脚本,用到tensorflow、keras等第三方库
    https://blog.csdn.net/jstlovely/article/details/121247764?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168110434116800227452800%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168110434116800227452800&......
  • 【Java基础】day16
    day16一、switch-case和if-else谁更快?switch-case在switch-case中,case的值是连续的话,会生成一个TableSwitch来进行优化,这样的情况下,只需要在表中进行判断即可。这里使用0-4的连续值来进行测试如果说多加几个Case的值,但是范围控制在比较小的范围时:这里使用0......
  • Java:谈谈线程池的使用?
    简介线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。核心参数1、核心线程数2、最大线程数3、空闲线程最大存活时间4、时间单位5、线程等待队列5、线程工厂......