互操作就是在 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 注解, 如 @EntityAllOpen 是为所标注的类去掉 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 类的静态方法
首先, 新建一个 ExampleKt.kt 的文件, 并新建一个 bar 函数:
- package demo
- class Foo
- fun bar(){
- println("这只是一个 bar 方法")
- }
然后, 在 Java 中调用这个函数
- package demo;
- public class Example {
- public static void main(String[] args) {
- demo.ExampleKtKt.bar();
- }
- }
当然, 可以使用 @JvmName 注解修改所生成的 Java 类的类名例如:
- @file:JvmName("Demo")
- package demo
那么在 Java 调用时就需要修改类名例如:
- public class Example {
- public static void main(String[] args) {
- demo.Demo.bar();
- }
- }
在多个文件中生成相同的 Java 类名 (包名相同并且类名相同或者有相同的 @JvmName 注解) 通常是错误的然而, 编译器能够生成一个单一的 Java 外观类, 它具有指定的名称且包含来自于所有文件中具有该名称的所有声明要生成这样的外观, 请在所有的相关文件中使用 @JvmMultifileClass 注解
- @file:JvmName("example")
- @file:JvmMultifileClass
- package demo
实例字段
如果需要在 Java 中将 Kotlin 属性作为字段暴露, 那么就需要使用 @JvmField 注解对其进行标注使用 @JvmField 注解标注后, 该字段将具有与底层属性相同的可见性如果一个属性有幕后字段 (Backing Field) 非私有的没有 open/override 或者 const 修饰符, 并且不是被委托的属性, 那么可以使用 @JvmField 注解该属性
首先, 新建一个 kt 类, 并添加如下代码
- class C(id: String) {
- @JvmField val ID = id
- }
然后在 Java 中调用该代码,
- class JavaClient {
- public String getID(C c) {
- return c.ID;
- }
- }
延迟初始化的属性 (在 Java 中) 也会暴露为字段, 该字段的可见性与 lateinit 属性的 setter 相同
静态字段
在命名对象或伴生对象时, 声明的 Kotlin 属性会在该命名对象或包含伴生对象的类中包含静态幕后字段通常这些字段是私有的, 但可以通过以下方式之一暴露出来
@JvmField 注解;
lateinit 修饰符;
const 修饰符
使用 @JvmField 标注的属性, 可以使其成为与属性本身具有相同可见性的静态字段例如:
- class Key(val value: Int) {
- companion object {@JvmField val COMPARATOR: Comparator < Key > =compareBy < Key > {
- it.value
- }
- }
- }
然后, 在 Java 代码中调用属性
- Key.COMPARATOR.compare(key1, key2);
- // Key 类中的 public static final 字段
在命名对象或者伴生对象中的一个延迟初始化的属性具有与属性 setter 相同可见性的静态幕后字段
- object Singleton {
- lateinit var provider: Provider
- }
然后, 在 Java 中使用该字段的属性
- // Java
- Singleton.provider = new Provider();
- // 在 Singleton 类中的 public static 非 - final 字段
用 const 标注的 (在类中以及在顶层的) 属性在 Java 中会成为静态字段, 首先新建一个 kt 文件
- object Obj {
- const val CONST = 1
- }
- class C {
- companion object {
- const val VERSION = 9
- }
- }
- const val MAX = 239
然后, 在 Java 中可以直接调用该属性即可
- int c = Obj.CONST;
- int d = ExampleKt.MAX;
- int v = C.VERSION;
静态方法
Kotlin 将包级函数表示为静态方法如果对这些函数使用 @JvmStatic 进行标注, 那么 Kotlin 还可以为在命名对象或伴生对象中定义的函数生成静态方法如果使用该注解, 那么编译器既会在相应对象的类中生成静态方法, 也会在对象自身中生成实例方法例如:
- class C {
- companion object {
- @JvmStatic fun foo() {}
- fun bar() {}
- }
- }
现在, foo()在 Java 中是静态的, 而 bar()不是静态的
- C.foo(); // 正确
- C.bar(); // 错误: 不是一个静态方法
- C.Companion.foo(); // 保留实例方法
- C.Companion.bar(); // 唯一的工作方式
对于命名对象, 也存在同样的规律
- object Obj {
- @JvmStatic fun foo() {}
- fun bar() {}
- }
在 Java 中使用
- Obj.foo(); // 没问题
- Obj.bar(); // 错误
- Obj.INSTANCE.bar(); // 没问题, 通过单例实例调用
- Obj.INSTANCE.foo(); // 也没问题
@JvmStatic 注解也可以应用于对象或伴生对象的属性, 使其 getter 和 setter 方法在该对象或包含该伴生对象的类中是静态成员
可见性
Kotlin 的可见性以下列方式映射到 Java 代码中
private 成员编译成 private 成员;
private 的顶层声明编译成包级局部声明;
protected 保持 protected(注意 Java 允许访问同一个包中其他类的受保护成员, 而 Kotlin 不能, 所以 Java 类会访问更广泛的代码);
internal 声明会成为 Java 中的 publicinternal 类的成员会通过名字修饰, 使其更难以在 Java 中意外使用到, 并且根据 Kotlin 规则使其允许重载相同签名的成员而互不可见;
public 保持 public
KClass
有时你需要调用有 KClass 类型参数的 Kotlin 方法 因为没有从 Class 到 KClass 的自动转换, 所以你必须通过调用 Class.kotlin 扩展属性的等价形式来手动进行转换例如:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
签名冲突
有时我们想让一个 Kotlin 中的命名函数在字节码中有另外一个 JVM 名称, 最突出的例子是由于类型擦除引发的
- fun List<String>.filterValid(): List<String>
- fun List<Int>.filterValid(): List<Int>
这两个函数不能同时定义在一个类中, 因为它们的 JVM 签名是一样的如果我们真的希望它们在 Kotlin 中使用相同的名称, 可以使用 @JvmName 去标注其中的一个(或两个), 并指定不同的名称作为参数例如:
- fun List<String>.filterValid(): List<String>
- @JvmName("filterValidInt")
- fun List<Int>.filterValid(): List<Int>
在 Kotlin 中它们可以用相同的名称 filterValid 来访问, 而在 Java 中, 它们分别是 filterValid 和 filterValidInt 同样的技巧也适用于属性中例如:
- val x: Int
- @JvmName("getX_prop")
- get() = 15
- fun getX() = 10
生成重载
通常, 如果你写一个有默认参数值的 Kotlin 函数, 在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见, 如果希望向 Java 调用者暴露多个重载, 可以使用 @JvmOverloads 注解该注解可以用于构造函数静态方法中, 但不能用于抽象方法和在接口中定义的方法
- class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
- @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
- }
- }
对于每一个有默认值的参数, 都会生成一个额外的重载, 这个重载会把这个参数和它右边的所有参数都移除掉在上例中, 会生成以下代码
- // 构造函数:
- Foo(int x, double y)
- Foo(int x)
- // 方法
- void f(String a, int b, String c) { }
- void f(String a, int b) { }
- void f(String a) { }
请注意, 如次构造函数中所述, 如果一个类的所有构造函数参数都有默认值, 那么会为其生成一个公有的无参构造函数, 此时就算没有 @JvmOverloads 注解也有效
受检异常
如上所述, Kotlin 没有受检异常 所以, 通常 Kotlin 函数的 Java 签名不会声明抛出异常, 于是如果我们有一个这样的 Kotlin 函数首先, 新建一个 kt 文件
- //// example.kt
- package demo
- fun foo() {
- throw IOException()
- }
然后, 在 Java 中调用它的时候, 需要使用 try{}catch{}来捕捉这个异常
- // Java
- try {
- demo.Example.foo();
- }
- catch (IOException e) { // 错误: foo() 未在 throws 列表中声明 IOException
- //
- }
因为 foo() 没有声明 IOException, 我们从 Java 编译器得到了一个报错消息 为了解决这个问题, 要在 Kotlin 中使用 @Throws 注解
- @Throws(IOException::class)
- fun foo() {
- throw IOException()
- }
空安全性
当从 Java 中调用 Kotlin 函数时, 没有任何方法可以阻止 Kotlin 中的空值传入 Kotlin 在 JVM 虚拟机中运行时会检查所有的公共函数, 可以检查非空值, 这时候就可以通过 NullPointerException 得到 Java 中的非空值代码
型变的泛型
当 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<? extends Super> (或者对于逆变定义的 Foo 生成 Foo<? super Bar>)当它是一个返回值时, 我们不生成通配符, 因为否则 Java 客户端将必须处理它们 (并且它违反常用 Java 编码风格) 因此, 我们的示例中的对应函数实际上翻译如下:
- // 作为返回类型没有通配符
- Box < Derived > boxDerived(Derived value) {}
- // 作为参数有通配符
- Base unboxBase(Box < ?extends Base > box) {}
注意: 当参数类型是 final 时, 生成通配符通常没有意义, 所以无论在什么地方 Box 始终转换为 Box 如果我们在默认不生成通配符的地方需要通配符, 我们可以使用 @JvmWildcard 注解:
- fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
- // 将被转换成
- // Box<? extends Derived> boxDerived(Derived value) { }
另一方面, 如果我们根本不需要默认的通配符转换, 我们可以使用 @JvmSuppressWildcards
- fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
- // 会翻译成
- // Base unboxBase(Box<Base> box) { }
注意:@JvmSuppressWildcards 不仅可用于单个类型参数, 还可用于整个声明(如函数或类), 从而抑制其中的所有通配符
Nothing 类型
类型 Nothing 是特殊的, 因为它在 Java 中没有自然的对应确实, 每个 Java 引用类型, 包括 java.lang.Void 都可以接受 null 值, 但是 Nothing 不行, 以为这种类型不能在 Java 中被准确表示这就是为什么在使用 Nothing 参数的地方 Kotlin 生成一个原始类型:
- fun emptyList(): List<Nothing> = listOf()
- // 会翻译成
- // List emptyList() { }
来源: http://click.aliyun.com/m/41362/