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

()和ArrayList()是不能区分的。这使得执行is检查不可能照顾到泛型,Kotlin只允许is检查星投影的泛型类型。

if(aisList)//错误:无法检查它是否真的是一个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 ArrayListrunnables=newArrayList();

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){

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(val value: T)

interface Base

class Derived : Base

fun boxDerived(value: Derived): Box = Box(value)

fun unboxBase(box: Box): Base = box.value

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

Box boxDerived(Derived value) { … }

Base unboxBase(Box 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<?extendsSuper>(或者对于逆变定义的Foo生成Foo<?superBar>)。当它是一个返回值时,则不生成通配符;否则,Java客户端必须处理它们(并且它违反了常用的Java编码风格)。因此,将示例中的对应函数实际上翻译如下:// 作为返回类型——没有通配符

Box boxDerived(Derived value) { … }

// 作为参数——有通配符

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

当参数类型是final时,生成通配符通常没有意义,所以无论在什么地方Box始终转换为Box。

如果在默认不生成通配符的地方需要通配符,则可以使用@JvmWildcard注解。

fun boxDerived(value: Derived): Box = Box(value) // 将被转换成

// Box extends Derived> boxDerived(Derived value) { … }

另外,如果根本不需要默认的通配符转换,则可以使用@JvmSuppressWildcards注解。

fun unboxBase(box: Box):

Base = box.value

// 会翻译成

// Base unboxBase(Box box) { … }

@JvmSuppressWildcards不仅可应用于单个类型参数,还可应用于整个声明(如函数或类),从而抑制其中的所有通配符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值