Kotlin的互操作——Kotlin与Java互相调用

2023-10-30

互操作就是在Kotlin中可以调用其他编程语言的接口,只要它们开放了接口,Kotlin就可以调用其成员属性和成员方法,这是其他编程语言所无法比拟的。同时,在进行Java编程时也可以调用Kotlin中的API接口。

Kotlin与Java互操作

1 Kotlin调用Java

Kotlin在设计时就考虑了与Java的互操作性。可以从Kotlin中自然地调用现有的Java代码,在Java代码中也可以很顺利地调用Kotlin代码。

【例1】在Kotlin中调用Java的Util的list库。

packagejqiang.Mutual.Kotlin
importjava.util.*
fundemo(source:List<Int>){
    vallist=ArrayList<Int>()
    for(iteminlist){
        list.add(item)
    }
    for(iin0..source.size-1){
        list[i]=source[i]
    }
}

基本的互操作行为如下:

1.属性读写

Kotlin可以自动识别Java中的getter/setter;在Java中可以过getter/setter操作Kotlin属性。

【例2】自动识别Java中的getter/setter。

packagejqiang.Mutual.Kotlin
importjava.util.*
funmain(args:Array<String>){
    valcalendar=Calendar.getInstance()
    println(calendar.firstDayOfWeek)
    if(calendar.firstDayOfWeek==1){// 调用getFirstDayOfWeek()方法
        calendar.firstDayOfWeek=2// 调用setFirstDayOfWeek()方法
    }
    println(calendar.firstDayOfWeek)
}

遵循Java约定的getter和setter方法(名称以get开头的无参数方法和以set开头的单参数方法)在Kotlin中表示为属性。如果Java类只有一个setter,那么它在Kotlin中不会作为属性可见,因为Kotlin目前不支持只写(set-only)属性。

2.空安全类型

Kotlin的空安全类型的原理是,Kotlin在编译过程中会增加一个函数调用,对参数类型或者返回类型进行控制,开发者可以在开发时通过注解@Nullable和@NotNull方式来弥补Java中空值异常。

Java中的任何引用都可能是null,这使得Kotlin对来自Java的对象进行严格的空安全检查是不现实的。Java声明的类型在Kotlin中称为平台类型,并会被特别对待。对这种类型的空检查要求会放宽,因此对它们的安全保证与在Java中相同。

【例3】空值实例。

vallist=ArrayList<String>()//非空(构造函数结果)
list.add("Item")
val size=list.size()//非空(原生Int)
Val item=list[0]//推断为平台类型(普通Java对象)

当调用平台类型变量的方法时,Kotlin不会在编译时报告可空性错误,但是在运行时调用可能会失败,因为空指针异常。

item.substring(1)//允许,如果item==null可能会抛出异常

平台类型是不可标识的,这意味着不能在代码中明确地写下它们。当把一个平台值赋给一个Kotlin变量时,可以依赖类型推断(该变量会具有所推断出的平台类型,如上例中item所具有的类型),或者选择我们所期望的类型(可空的或非空类型均可)。

val nullable:String?=item//允许,没有问题
Val notNull:String=item//允许,运行时可能失败

如果选择非空类型,编译器会在赋值时触发一个断言,这样可以防止Kotlin的非空变量保存空值。当把平台值传递给期待非空值等的Kotlin函数时,也会触发一个断言。总的来说,编译器尽力阻止空值通过程序向远传播(由于泛型的原因,有时这不可能完全消除)。

3.返回void的方法

如果在Java中返回void,那么Kotlin返回的就是Unit。如果在调用时返回void,那么Kotlin会事先识别该返回值为void。

4.注解的使用

@JvmField是Kotlin和Java互相操作属性经常遇到的注解;@JvmStatic是将对象方法编译成Java静态方法;@JvmOverloads主要是Kotlin定义默认参数生成重载方法;@file:JvmName指定Kotlin文件编译之后生成的类名。

5.NoArg和AllOpen

数据类本身属性没有默认的无参数的构造方法,因此Kotlin提供一个NoArg插件,支持JPA注解,如@Entity。AllOpen是为所标注的类去掉final,目的是为了使该类允许被继承,且支持Spring注解,如@Componet;支持自定义注解类型,如@Poko。

6.泛型

Kotlin中的通配符“”代替Java中的“?”;协变和逆变由Java中的extends和super变成了out和in,如ArrayList;在Kotlin中没有Raw类型,如Java中的List对应于Kotlin就是List<>。

与Java一样,Kotlin在运行时不保留泛型,也就是对象不携带传递到它们的构造器中的类型参数的实际类型,即ArrayList()和ArrayList()是不能区分的。这使得执行is检查不可能照顾到泛型,Kotlin只允许is检查星投影的泛型类型。

if(aisList<Int>)//错误:无法检查它是否真的是一个Int列表
if(aisList<*>)//OK:不保证列表的内容
7.SAM转换

就像Java 8一样,Kotlin支持SAM转换,这意味着Kotlin函数字面值可以被自动转换成只有一个非默认方法的Java接口的实现,只要这个方法的参数类型能够与这个Kotlin函数的参数类型相匹配就行。

【例4】首先使用Java创建一个SAMInJava类,然后通过Kotlin调用Java中的接口。

Java代码:

packagejqiang.Mutual.Java;
import java.util.ArrayList;
public class SAMInJava{
    private ArrayList<Runnable>runnables=newArrayList<Runnable>();
    public void addTask(Runnablerunnable){
        runnables.add(runnable);
System.out.println("add:"+runnable+",size"+runnables.size());
    }
    Public void removeTask(Runnablerunnable){
        runnables.remove(runnable);
System.out.println("remove:"+runnable+"size"+runnables.size());
    }
}

Kotlin代码:

packagejqiang.Mutual.Kotlin
importjqiang.Mutual.Java.SAMInJava
funmain(args:Array<String>){
    varsamJava=SAMInJava()
    vallamba={
        print("hello")
    }
    samJava.addTask(lamba)
    samJava.removeTask(lamba)
}

运行结果如下:

add:jqiang.Mutual.Kotlin.SamKt$sam$Runnable$bef91c64@63947c6bsize1
remove:jqiang.Mutual.Kotlin.SamKt$sam$Runnable$bef91c64@2b193f2dsize1

如果Java类有多个接受函数式接口的方法,那么可以通过使用将Lambda表达式转换为特定的SAM类型的适配器函数来选择需要调用的方法。这些适配器函数也会按需由编译器生成。

vallamba={
    print("hello")
}
samJava.addTask(lamba)

SAM转换只适用于接口,而不适用于抽象类,即使这些抽象类只有一个抽象方法。此功能只适用于Java互操作;因为Kotlin具有合适的函数类型,所以不需要将函数自动转换为Kotlin接口的实现,因此不受支持。

2 Java调用Kotlin

在Java中可以轻松地调用Kotlin代码。

1.属性

Kotlin属性会被编译成以下Java元素:

  • getter方法,其名称通过加前缀get得到;
  • setter方法,其名称通过加前缀set得到(只适用于var属性);
  • 私有字段,与属性名称相同(仅适用于具有幕后字段的属性)。

【例5】将Kotlin变量编译成Java中的变量声明。

Kotlin部分代码:

varfirstName:String
Java部分代码:
privateStringfirstName;
publicStringgetFirstName(){
    returnfirstName;
}
publicvoidsetFirstName(StringfirstName){
    this.firstName=firstName;
}

如果属性名称是以is开头的,则使用不同的名称映射规则:getter的名称与属性名称相同,并且setter的名称是通过将is替换成set获得的。例如,对于属性isOpen,其getter会称作isOpen(),而其setter会称作setOpen()。这一规则适用于任何类型的属性,并不仅限于Boolean。

2.包级函数

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

【例6】包级函数调用。

Kotlin部分代码:

packagejqiang.Mutual.Kotlin
funbar(){
    println("这只是一个bar方法")
}

Java部分代码:
packagejqiang.Mutual.Java;
publicclassexample{
publicstaticvoidmain(String[]args){
jqiang.Mutual.Kotlin.ExampleKt.bar();
}
}

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

@file:JvmName("example")
packagejqiang.Mutual.Kotlin

那么Java调用时就需要修改类名:

jqiang.Mutual.Kotlin.example.bar();

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

@file:JvmName("example")
@file:JvmMultifileClass
packagejqiang.Mutual.Kotlin
3.实例字段

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

4.静态方法

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

classC{
    companionobject{
        @JvmStaticfunfoo(){}
        funbar(){}
    }
}

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

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

对于命名对象也同样:

objectObj{
    @JvmStaticfunfoo(){}
    funbar(){}
}

在Java中:

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

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

5.可见性

Kotlin的可见性以下列方式映射到Java。

(1)private成员被编译成private成员。

(2)private的顶层声明被编译成包级局部声明。

(3)protected依然保持protected(注意,Java允许访问同一个包中其他类的受保护成员,而Kotlin则不允许,所以Java类会访问更广泛的代码)。

(4)internal声明会成为Java中的public。internal类的成员会通过名字修饰,使其更难以在Java中被意外使用到,并且根据Kotlin规则使其允许重载相同签名的成员而互不可见。

(5)public依然保持public。

6.空安全性

当从Java中调用Kotlin函数时,没有任何方法可以阻止Kotlin中的空值传入。Kotlin在JVM虚拟机中运行时会检查所有的公共函数,可以检查非空值,这时候就可以通过NullPointerException得到Java中的非空值代码。

7.型变的泛型

当Kotlin使用了声明处型变时,可以通过两种方式从Java代码中看到它们的用法。假设有以下类和两个使用它的函数:

class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value

将这两个函数转换成Java代码:

Box<Derived> boxDerived(Derived value) { … } 
Base unboxBase(Box<Base> box) { … }

在Kotlin中可以这样写:unboxBase(boxDerived(“s”)),但是在Java中是行不通的,因为在Java中Box类在其泛型参数T上是不型变的,于是Box并不是Box的子类。要使其在Java中工作,需要按以下方式定义unboxBase:

Base unboxBase(Box<? extends Base> box) { … }

这里使用Java的通配符类型(? extends Base)通过使用处型变来模拟声明处型变,因为在Java中只能这样。

当它作为参数出现时,为了让Kotlin的API在Java中工作,对于协变定义的Box生成Box作为Box

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin的互操作——Kotlin与Java互相调用 的相关文章

随机推荐

  • 基于springboot+vue的开源mes系统

    真正的大师 永远都怀着一颗学徒的心 一 项目简介 基于springboot vue的开源mes系统 二 实现功能 常规管理 现场数据采集 系统管理 菜单管理 用户管理 日志管理 角色管理 基础数据配置平台 基础数据维护 物料管理 物料台账
  • 程序员常去的个顶级开发社区

    程序员常去的个顶级开发社区 Stack Overflow 9月份 Stack Overflow也将迎来其6岁的生日 毫无疑问 Stack Overflow是全球最受程序员欢迎的开发社区 而且也是内容最丰富的社区之一 官方网站 http st
  • checkpatch海思SDK代码遇见的常见错误《一》

    01 WARNING AVOID EXTERNS externs should be avoided in c files 3 FILE sample region sample region c 3 extern C ifdef cplu
  • C++面试题分享

    在牛客网偶然间翻到的一个大佬总结的有关C 面试题的一个专栏 该专刊囊括了C语言 C 操作系统 计算机网络 嵌入式 算法与数据结构 数据库等一系列知识点 总结出了高频面试考点 附有答案 共计273道 在这里给大家分享一下 链接 蒋豆芽面试题专
  • gitlab--基础--5.4--CICD--variables

    gitlab 基础 5 4 CICD variables 1 variables的保留字 1 1 CI 标识该job是在CI环境中执行 1 2 CI COMMIT REF NAME 用于构建项目的分支或tag 的名称 1 3 CI COMM
  • Could not find a package configuration file provided by "ignition-transport1"

    一 错误 按照Gazebo官网教程进行code introspection环境配置 在对代码进行编译时报错如下 二 错误原因 cmakelists txt里设置的ignition transport版本为ignition transport
  • 解决方案:fatal error: openssl/bio.h: 没有那个文件或目录

    出现报错如下 出现该错误的原因有两个 没有安装openssl或者libssl dev库 Libssl dev版本过高 需要降级 一 没有安装openssl或者libssl dev库 使用指令安装openssl 我的是已经安装完成了 所以再把
  • catch抛出异常finally还执行吗

    在面试的时候 经常会问到一些很基础的知识 比如有没接触过try catch finally 他们的执行顺序是怎么样的呢 你对此嗤之以鼻 这不是很简单吗 按照顺序执就行了 没错 可是如果每一块有return 并且有抛异常 扑捉异常的情况呢 估
  • 三步搞定ABAP DOI操作EXCEL

    前言 ABAP可以使用OLE与DOI两种方式实现操作EXCEL 使用OLE时 每个单元格的值和样式都需要写代码实现 特别是对于不规则的格式 代码量巨大 而DOI是从服务器已经上传的EXCEL模板中下载模板然后打开修改实现数据保存 当然 也可
  • sum(1),sum(2,3,4),sum(2)(3)(4)

  • 高级人工智能课程笔记

    课程部分笔记 依据 人工智能 一种现代化方法 第三版 目录 智能概述 搜索search Uninformed Search Informed Search 约束满足问题CSP MDP 值迭代方法 策略迭代 RL 朴素贝叶斯 其他 智能概述
  • el-input针对手机号的校验优化

    注意 以下代码均只展示与本文相符的内容 并不提供完整项目代码 先看代码 tool js文件 function isvalidPhone str const reg 1 3 4 5 7 8 0 9 d 8 return reg test st
  • tomcat 修改默认访问项目名称和项目发布路径

    摘要 本次主要介绍tomcat设置访问的默认项目的名称和项目存放的路径 1 修改项目发布路径 tomcat默认的而发布路径为 tomcat webapps 目录 但是这个目录下有一些默认的项目 在tomcat启动的时候会跟着一起加载 如果不
  • 数据库连接池的实现及原理

    对于一个简单的数据库应用 由于对于数据库的访问不是很频繁 这时可以简单地在需要访问数据库时 就新创建一个连接 用完后就关闭它 这样做也不会带来什么明显的性能上的开销 但是对于一个复杂的数据库应用 情况就完全不同了 频繁的建立 关闭连接 会极
  • Docker (一)如何打dockerfile

    熟悉docker 是高级java必备的技术素质 在面试中 经常会有公司问到 你会打dockerfile吗 面试中很少问及docker的其他知识点 那是因为docker是运维范围内的事 如果你的公司拥有强大的运维平台的话 基本上打docker
  • windows下编译dlib

    dlib 1 下载dlib源码 dlib18 17 http pan baidu com s 1gey9Wd1 2 解压源码包 3 打开cmake 设置source code路径为解压目录 新建生成目录 起名为build 设为二进制生成目录
  • 如何提高训练模型准确率

    如何提高训练模型准确率 原文链接 https blog csdn net Winteeena article details 78997696 提升一个模型的表现有时很困难 尝试所有曾学习过的策略和算法 但模型正确率并没有改善 这才是考验真
  • call、apply、bind的基本概念

    const dog name 旺财 getName console log 我的名字叫 this name setFood food console log 我的名字叫 this name 我喜欢吃 food const eat name
  • 聚簇索引与主键的选择

    聚簇索引与主键的选择 一 什么是聚簇索引 二 什么是非聚簇索引 1 InnoDB引擎中 2 MyISAM引擎中 三 聚簇索引的优劣与主键选择的关系 一 什么是聚簇索引 首先 聚簇索引不是一种单独的索引类型 其实是数据的存储方式 聚簇索引将数
  • Kotlin的互操作——Kotlin与Java互相调用

    互操作就是在Kotlin中可以调用其他编程语言的接口 只要它们开放了接口 Kotlin就可以调用其成员属性和成员方法 这是其他编程语言所无法比拟的 同时 在进行Java编程时也可以调用Kotlin中的API接口 Kotlin与Java互操作