内容简介:Kotlin与Java互操作
互操作就是在Kotlin中可以调用其他编程语言的接口,只要它们开放了接口,Kotlin就可以调用其成员属性和成员方法,这是其他编程语言所无法比拟的。同时,在进行 Java 编程时也可以调用Kotlin中的API接口。
Kotlin调用Java
Kotlin在设计时就考虑了与Java的互操作性。可以从Kotlin中自然地调用现有的Java代码,在Java代码中也可以很顺利地调用Kotlin代码。例如,在Kotlin中调用Java的Util的list库。
import java.util.* fun demo(source: List<Int>) { val list = ArrayList<Int>() // “for”-循环用于 Java 集合: for (item in source) { list.add(item) } // 操作符约定同样有效: for (i in 0..source.size - 1) { list[i] = source[i] // 调用 get 和 set } }
基本的互操作行为如下:
属性读写
Kotlin可以自动识别Java中的getter/setter函数,而在Java中可以过getter/setter操作Kotlin属性。
import java.util.Calendar fun calendarDemo() { val calendar = Calendar.getInstance() if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // 调用 getFirstDayOfWeek() calendar.firstDayOfWeek = Calendar.MONDAY // 调用ll setFirstDayOfWeek() } if (!calendar.isLenient) { // 调用 isLenient() calendar.isLenient = true // 调用 setLenient() } }
循Java约定的getter和setter方法(名称以get开头的无参数方法和以set开头的单参数方法)在Kotlin中表示为属性。如果Java类只有一个setter,那么它在Kotlin中不会作为属性可见,因为Kotlin目前不支持只写(set-only)属性。
空安全类型
Kotlin的空安全类型的原理是,Kotlin在编译过程中会增加一个函数调用,对参数类型或者返回类型进行控制,开发者可以在开发时通过注解@Nullable和@NotNull方式来限制Java中空值异常。
Java中的任何引用都可能是null,这使得Kotlin对来自Java的对象进行严格的空安全检查是不现实的。Java声明的类型在Kotlin中称为平台类型,并会被特别对待。对这种类型的空检查要求会放宽,因此对它们的安全保证与在Java中相同。
val list = 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函数时,也会触发一个断言。总的来说,编译器尽力阻止空值的传播(由于泛型的原因,有时这不可能完全消除)。
平台类型标识法
如上所述,平台类型不能在程序中显式表述,因此在语言中没有相应语法。 然而,编译器和 IDE 有时需要(在错误信息中、参数信息中等)显示他们,Koltin提供助记符来表示他们:
- T! 表示“T 或者 T?”;
- (Mutable)Collection! 表示“可以可变或不可变、可空或不可空的 T 的 Java 集合”;
- Array<(out) T>! 表示“可空或者不可空的 T(或 T 的子类型)的 Java 数组”。
可空注解
由于泛型的原因,Kotlin在编译时可能出现空异常,而使用空注解可以有效的解决这一情况。编译器支持多种可空性注解:
- JetBrains :org.jetbrains.annotations 包中的 @Nullable 和 @NotNull;
- Android :com.android.annotations 和 android.support.annotations;
- JSR-305 :javax.annotation;
- FindBugs :edu.umd.cs.findbugs.annotations;
- Eclipse :org.eclipse.jdt.annotation;
- Lombok :lombok.NonNull;
JSR-305 支持
在JSR-305中,定义的 @Nonnull 注解来表示 Java 类型的可空性。
如果 @Nonnull(when = …) 值为 When.ALWAYS,那么该注解类型会被视为非空;When.MAYBE 与 When.NEVER 表示可空类型;而 When.UNKNOWN 强制类型为平台类型。
可针对 JSR-305 注解编译库,但不需要为库的消费者将注解构件(如 jsr305.jar)指定为编译依赖。Kotlin 编译器可以从库中读取 JSR-305 注解,并不需要该注解出现在类路径中。
自 Kotlin 1.1.50 起, 也支持 自定义可空限定符 (KEEP-79)
类型限定符
如果一个注解类型同时标注有 @TypeQualifierNickname 与 JSR-305 @Nonnull(或者它的其他别称,如 @CheckForNull),那么该注解类型自身将用于 检索精确的可空性,且具有与该可空性注解相同的含义。
@TypeQualifierNickname @Nonnull(when = When.ALWAYS) @Retention(RetentionPolicy.RUNTIME) public @interface MyNonnull { } @TypeQualifierNickname @CheckForNull // 另一个类型限定符别称的别称 @Retention(RetentionPolicy.RUNTIME) public @interface MyNullable { } interface A { @MyNullable String foo(@MyNonnull String x); // 在 Kotlin(严格模式)中:`fun foo(x: String): String?` String bar(List<@MyNonnull String> x); // 在 Kotlin(严格模式)中:`fun bar(x: List<String>!): String!` }
类型限定符默认值
@TypeQualifierDefault 引入应用时在所标注元素的作用域内定义默认可空性的注解。这些注解类型应自身同时标注有 @Nonnull(或其别称)与 @TypeQualifierDefault(…) 注解, 后者带有一到多个 ElementType 值。
- ElementType.METHOD 用于方法的返回值;
- ElementType.PARAMETER 用于值参数;
- ElementType.FIELD 用于字段;
- ElementType.TYPE_USE(自 1.1.60 起)适用于任何类型,包括类型参数、类型参数的上界与通配符类型。
当类型并未标注可空性注解时使用默认可空性,并且该默认值是由最内层标注有带有与所用类型相匹配的 ElementType 的类型限定符默认注解的元素确定。
@Nonnull @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) public @interface NonNullApi { } @Nonnull(when = When.MAYBE) @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE}) public @interface NullableApi { } @NullableApi interface A { String foo(String x); // fun foo(x: String?): String? @NotNullApi // 覆盖来自接口的默认值 String bar(String x, @Nullable String y); // fun bar(x: String, y: String?): String // 由于 `@NullableApi` 具有 `TYPE_USE` 元素类型, // 因此认为 List<String> 类型参数是可空的: String baz(List<String> x); // fun baz(List<String?>?): String? // “x”参数仍然是平台类型,因为有显式 // UNKNOWN 标记的可空性注解: String qux(@Nonnull(when = When.UNKNOWN) String x); // fun baz(x: String!): String? }
也支持包级的默认可空性:
@NonNullApi // 默认将“test”包中所有类型声明为不可空 package test;
@UnderMigration 注解
库的维护者可以使用 @UnderMigration 注解(在单独的构件 kotlin-annotations-jvm 中提供)来定义可为空性类型限定符的迁移状态。
@UnderMigration(status = …) 中的状态值指定了编译器如何处理 Kotlin 中注解类型的不当用法(例如,使用 @MyNullable 标注的类型值作为非空值):
- MigrationStatus.STRICT 使注解像任何纯可空性注解一样工作,即对不当用法报错并影响注解声明内的类型在 Kotlin中的呈现;
- 对于 MigrationStatus.WARN,不当用法报为警告而不是错误; 但注解声明内的类型仍是平台类型;
- MigrationStatus.IGNORE 则使编译器完全忽略可空性注解。
库的维护者还可以将 @UnderMigration 状态添加到类型限定符别称与类型限定符默认值中。例如:
@Nonnull(when = When.ALWAYS) @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) @UnderMigration(status = MigrationStatus.WARN) public @interface NonNullApi { } // 类中的类型是非空的,但是只报警告 // 因为 `@NonNullApi` 标注了 `@UnderMigration(status = MigrationStatus.WARN)` @NonNullApi public class Test {}
注意:可空性注解的迁移状态并不会从其类型限定符别称继承,而是适用于默认类型限定符的用法。如果默认类型限定符使用类型限定符别称,并且它们都标注有 @UnderMigration,那么使用默认类型限定符的状态。
返回void的方法
如果在Java中返回void,那么Kotlin返回的就是Unit。如果在调用时返回void,那么Kotlin会事先识别该返回值为void。
注解的使用
@JvmField是Kotlin和Java互相操作属性经常遇到的注解;@JvmStatic是将对象方法编译成Java静态方法;@JvmOverloads主要是Kotlin定义默认参数生成重载方法;@file:JvmName指定Kotlin文件编译之后生成的类名。
NoArg和AllOpen
数据类本身属性没有默认的无参数的构造方法,因此Kotlin提供一个NoArg插件,支持JPA注解,如@Entity。AllOpen是为所标注的类去掉final,目的是为了使该类允许被继承,且支持Spring注解,如@Componet;支持自定义注解类型,如@Poko。
泛型
Kotlin 的泛型与 Java 有点不同,读者可以具体参考泛型章节。Kotlin中的通配符“”代替Java中的“?”;协变和逆变由Java中的extends和super变成了out和in,如ArrayList;在Kotlin中没有Raw类型,如Java中的List对应于Kotlin就是List<>。
与Java一样,Kotlin在运行时不保留泛型,也就是对象不携带传递到它们的构造器中的类型参数的实际类型,即ArrayList()和ArrayList()是不能区分的。这使得执行is检查不可能照顾到泛型,Kotlin只允许is检查星投影的泛型类型。
if (a is List<Int>) // 错误:无法检查它是否真的是一个 Int 列表 // but if (a is List<*>) // OK:不保证列表的内容
Java数组
与 Java 不同,Kotlin 中的数组是不型变的。这意味着 Kotlin 不允许我们把一个 Array 赋值给一个 Array, 从而避免了可能的运行时故障。Kotlin 也禁止我们把一个子类的数组当做超类的数组传递给 Kotlin 的方法, 但是对于 Java 方法,这是允许的(通过 Array<(out) String>! 这种形式的平台类型)。
Java 平台上,数组会使用原生数据类型以避免装箱/拆箱操作的开销。 由于 Kotlin 隐藏了这些实现细节,因此需要一个变通方法来与 Java 代码进行交互。 对于每种原生类型的数组都有一个特化的类(IntArray、 DoubleArray、 CharArray 等等)来处理这种情况。 它们与 Array 类无关,并且会编译成 Java 原生类型数组以获得最佳性能。
例如,假设有一个接受 int 数组索引的 Java 方法。
public class JavaArrayExample { public void removeIndices(int[] indices) { // 在此编码…… } }
在 Kotlin 中调用该方法时,你可以这样传递一个原生类型的数组。
val javaObj = JavaArrayExample() val array = intArrayOf(0, 1, 2, 3) javaObj.removeIndices(array) // 将 int[] 传给方法
当编译为 JVM 字节代码时,编译器会优化对数组的访问,这样就不会引入任何开销。
val array = arrayOf(1, 2, 3, 4) array[x] = array[x] * 2 // 不会实际生成对 get() 和 set() 的调用 for (x in array) { // 不会创建迭代器 print(x) }
即使当我们使用索引定位时,也不会引入任何开销:
for (i in array.indices) {// 不会创建迭代器 array[i] += 2 }
最后,in-检测也没有额外开销:
if (i in array.indices) { // 同 (i >= 0 && i < array.size) print(array[i]) }
Java 可变参数
Java 类有时声明一个具有可变数量参数(varargs)的方法来使用索引。
public class JavaArrayExample { public void removeIndicesVarArg(int... indices) { // 函数体…… } }
在这种情况下,你需要使用展开运算符 * 来传递 IntArray。
val javaObj = JavaArrayExample() val array = intArrayOf(0, 1, 2, 3) javaObj.removeIndicesVarArg(*array)
目前,无法传递 null 给一个声明为可变参数的方法。
SAM转换
就像Java 8一样,Kotlin支持SAM转换,这意味着Kotlin函数字面值可以被自动转换成只有一个非默认方法的Java接口的实现,只要这个方法的参数类型能够与这个Kotlin函数的参数类型相匹配就行。
首先使用Java创建一个SAMInJava类,然后通过Kotlin调用Java中的接口。
import java.util.ArrayList; public class SAMInJava{ private ArrayList<Runnable>runnables=new ArrayList<Runnable>(); public void addTask(Runnable runnable){ runnables.add(runnable); System.out.println("add:"+runnable+",size"+runnables.size()); } Public void removeTask(Runnable runnable){ runnables.remove(runnable); System.out.println("remove:"+runnable+"size"+runnables.size()); } }
然后在Kotlin中调用该Java接口。
fun main(args: Array<String>) { var samJava=SAMJava() val lamba={ print("hello") } samJava.addTask(lamba) samJava.removeTask(lamba) }
运行结果为:
add:SAMKotlinKt$sam$Runnable$8b8e16f1@4617c264,size1 remove:SAMKotlinKt$sam$Runnable$8b8e16f1@36baf30csize1
如果Java类有多个接受函数式接口的方法,那么可以通过使用将Lambda表达式转换为特定的SAM类型的适配器函数来选择需要调用的方法。
val lamba={ print("hello") } samJava.addTask(lamba)
**注意:**SAM转换只适用于接口,而不适用于抽象类,即使这些抽象类只有一个抽象方法。此功能只适用于Java互操作;因为Kotlin具有合适的函数类型,所以不需要将函数自动转换为Kotlin接口的实现,因此不受支持。
除此之外,Kotlin调用Java还有很多的内容,读者可以通过下面的链接来了解: Kotlin调用Java
Java调用Kotlin
Java 可以轻松调用 Kotlin 代码。
属性
Kotlin属性会被编译成以下Java元素:
- getter方法,其名称通过加前缀get得到;
- setter方法,其名称通过加前缀set得到(只适用于var属性);
- 私有字段,与属性名称相同(仅适用于具有幕后字段的属性)。
例如,将Kotlin变量编译成Java中的变量声明。
private String firstName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; }
如果属性名称是以is开头的,则使用不同的名称映射规则:getter的名称与属性名称相同,并且setter的名称是通过将is替换成set获得的。例如,对于属性isOpen,其getter会称作isOpen(),而其setter会称作setOpen()。这一规则适用于任何类型的属性,并不仅限于Boolean。
包级函数
例如,在org.foo.bar 包内的 example.kt 文件中声明的所有的函数和属性,包括扩展函数, 该 类会编译成一个名为 org.foo.bar.ExampleKt 的 Java 类的静态方法。
package demo class Foo fun bar() { println("这只是一个bar方法") }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript骚操作之操作符
- Go 语言操作 MySQL 之 事务操作
- C# 数据操作系列 - 1. SQL基础操作
- Vim 跨行操作与 Ex 命令操作范围
- 并发环境下,先操作数据库还是先操作缓存?
- 关于HBase Shell基本操作的表操作示例
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。