内容简介: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基本操作的表操作示例
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!