概述
kotlin第八天:异常、java和kotlin互操作
- 异常
- 异常类
- Try 是一个表达式
- 受检的异常
- Nothing 类型
- java 和kotlin互操作
- 在 Kotlin 中调用 Java 代码
- 已映射类型
- Kotlin 中的 Java 泛型
- Java 数组
- Java 可变参数
- 操作符
- 受检异常
- 对象方法
- 从 Java 类继承
- Java 反射
- SAM 转换
- 在 Kotlin 中使用 JNI
- Java 中调用 Kotlin
- 属性
- 包级函数
- 实例字段
- 静态字段
- 静态方法
- 可见性
- 生成重载
- 受检异常
- 型变的泛型
- Nothing 类型翻译
异常
异常类
Kotlin 中所有异常类都是 Throwable 类的子孙类。 每个异常都有消息、堆栈回溯信息以及可选的原因。
使用 throw-表达式来抛出异常:
throw Exception("Hi There!")
使用 try-表达式来捕获异常:
try {
// 一些代码
}
catch (e: SomeException) {
// 处理程序
}
finally {
// 可选的 finally 块
}
可以有零到多个 catch 块。finally 块可以省略。 但是 catch 与 finally 块至少应该存在一个。
Try 是一个表达式
try 是一个表达式,即它可以有一个返回值:
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
try-表达式的返回值是 try 块中的最后一个表达式或者是(所有)catch 块中的最后一个表达式。 finally 块中的内容不会影响表达式的结果。
受检的异常
Kotlin 没有受检的异常。这其中有很多原因,但我们会提供一个简单的例子。
以下是 JDK 中 StringBuilder 类实现的一个示例接口:
Appendable append(CharSequence csq) throws IOException;
这个签名是什么意思? 它是说,每次我追加一个字符串到一些东西(一个 StringBuilder、某种日志、一个控制台等)上时我就必须捕获那些 IOException。 为什么?因为它可能正在执行 IO 操作(Writer 也实现了 Appendable)…… 所以它导致这种代码随处可见的出现:
try {
log.append(message)
}
catch (IOException e) {
// 必须要安全
}
这并不好,参见《Effective Java》第三版 第 77 条:不要忽略异常。
Bruce Eckel 在《Java 是否需要受检的异常?》(Does Java need Checked Exceptions?) 中指出:
通过一些小程序测试得出的结论是异常规范会同时提高开发者的生产力与代码质量,但是大型软件项目的经验表明一个不同的结论——生产力降低、代码质量很少或没有提高。
其他相关引证:
《Java 的受检异常是一个错误》(Java’s checked exceptions were a mistake)(Rod Waldhoff)
《受检异常的烦恼》(The Trouble with Checked Exceptions)(Anders Hejlsberg)
Nothing 类型
在 Kotlin 中 throw 是表达式,所以你可以使用它(比如)作为 Elvis 表达式的一部分:
val s = person.name ?: throw IllegalArgumentException("Name required")
throw 表达式的类型是特殊类型 Nothing。 该类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing 来标记一个永远不会返回的函数:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
当你调用该函数时,编译器会知道执行不会超出该调用:
val s = person.name ?: fail("Name required")
println(s) // 在此已知“s”已初始化
可能会遇到这个类型的另一种情况是类型推断。这个类型的可空变体 Nothing? 有一个可能的值是 null。如果用 null 来初始化一个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing? 类型:
val x = null // “x”具有类型 `Nothing?`
val l = listOf(null) // “l”具有类型 `List<Nothing?>
java 和kotlin互操作
在 Kotlin 中调用 Java 代码
几乎所有 Java 代码都可以使用而没有任何问题:
setter和getter
java代码中的setter,getter按照标准的写法书写,在kotlin中也是可以按setter,getter调用的。
void Unit
java中的void和unit 对应
关键字
一些 Kotlin 关键字在 Java 中是有效标识符:in、 object、 is 等等。 如果一个 Java 库使用了 Kotlin 关键字作为方法,你仍然可以通过反引号(`)字符转义它来调用该方法:
foo.`is`(bar)
空安全
kotlin中调用java,不能保证空安全
已映射类型
Java 类型 | Kotlin 类型 |
---|---|
byte | kotlin.Byte |
short | kotlin.Short |
int | kotlin.Int |
long | kotlin.Long |
char | kotlin.Char |
float | kotlin.Float |
double | kotlin.Double |
boolean | kotlin.Boolean |
一些非原生的内置类型也会作映射:
Java 类型 | Kotlin 类型 |
---|---|
java.lang.Object | kotlin.Any! |
java.lang.Cloneable | kotlin.Cloneable! |
java.lang.Comparable | kotlin.Comparable! |
java.lang.Enum | kotlin.Enum! |
java.lang.Annotation | kotlin.Annotation! |
java.lang.Deprecated | kotlin.Deprecated! |
java.lang.CharSequence | kotlin.CharSequence! |
java.lang.String | kotlin.String! |
java.lang.Number | kotlin.Number! |
java.lang.Throwable | kotlin.Throwable! |
Java 的装箱原始类型映射到可空的 Kotlin 类型:
Java type | Kotlin type |
---|---|
java.lang.Byte | kotlin.Byte? |
java.lang.Short | kotlin.Short? |
java.lang.Integer | kotlin.Int? |
java.lang.Long | kotlin.Long? |
java.lang.Character | kotlin.Char? |
java.lang.Float | kotlin.Float? |
java.lang.Double | kotlin.Double? |
java.lang.Boolean | kotlin.Boolean? |
集合类型在 Kotlin 中可以是只读的或可变的,因此 Java 集合类型作如下映射: (下表中的所有 Kotlin 类型都驻留在 kotlin.collections包中):
Java 类型 | Kotlin 只读类型 | Kotlin 可变类型 | 加载的平台类型 |
---|---|---|---|
Iterator | Iterator | MutableIterator | (Mutable)Iterator! |
Iterable | Iterable | MutableIterable | (Mutable)Iterable! |
Collection | Collection | MutableCollection | (Mutable)Collection! |
Set | Set | MutableSet | (Mutable)Set! |
List | List | MutableList | (Mutable)List! |
ListIterator | ListIterator | MutableListIterator | (Mutable)ListIterator! |
Map<K, V> | Map<K, V> | MutableMap<K, V> | (Mutable)Map<K, V>! |
Map.Entry<K, V> | Map.Entry<K, V> | MutableMap.MutableEntry<K,V> | (Mutable)Map.(Mutable)Entry<K, V>! |
Java 的数组按下文所述映射:
Java 类型 | Kotlin 类型 |
---|---|
int[] | kotlin.IntArray! |
String[] | kotlin.Array<(out) String>! |
注意:这些 Java 类型的静态成员不能在相应 Kotlin 类型的伴生对象中直接访问。要调用它们,请使用 Java 类型的完整限定名,例如 java.lang.Integer.toHexString(foo)。
Kotlin 中的 Java 泛型
Kotlin 的泛型与 Java 有点不同(参见泛型)。当将 Java 类型导入 Kotlin 时,我们会执行一些转换:
Java 的通配符转换成类型投影,
Foo<? extends Bar> 转换成 Foo<out Bar!>!,
Foo<? super Bar> 转换成 Foo<in Bar!>!;
Java的原始类型转换成星投影,
List 转换成 List<*>!,即 List<out Any?>!。
和 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[1] = array[1] * 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 给一个声明为可变参数的方法。
操作符
由于 Java 无法标记用于运算符语法的方法,Kotlin 允许具有正确名称和签名的任何 Java 方法作为运算符重载和其他约定(invoke() 等)使用。 不允许使用中缀调用语法调用 Java 方法。
受检异常
在 Kotlin 中,所有异常都是非受检的,这意味着编译器不会强迫你捕获其中的任何一个。 因此,当你调用一个声明受检异常的 Java 方法时,Kotlin 不会强迫你做任何事情:
fun render(list: List<*>, to: Appendable) {
for (item in list) {
to.append(item.toString()) // Java 会要求我们在这里捕获 IOException
}
}
对象方法
当 Java 类型导入到 Kotlin 中时,类型 java.lang.Object 的所有引用都成了 Any。 而因为 Any 不是平台指定的,它只声明了 toString()、hashCode() 和 equals() 作为其成员, 所以为了能用到 java.lang.Object 的其他成员,Kotlin 要用到扩展函数。
wait()/notify()
类型 Any 的引用没有提供 wait() 与 notify() 方法。通常不鼓励使用它们,而建议使用 java.util.concurrent。 如果确实需要调用这两个方法的话,那么可以将引用转换为 java.lang.Object:
(foo as java.lang.Object).wait()
getClass()
要取得对象的 Java 类,请在类引用上使用 java 扩展属性:
val fooClass = foo::class.java
上面的代码使用了自 Kotlin 1.1 起支持的绑定的类引用。你也可以使用 javaClass 扩展属性:
val fooClass = foo.javaClass
clone()
要覆盖 clone(),需要继承 kotlin.Cloneable:
class Example : Cloneable {
override fun clone(): Any { …… }
}
不要忘记《Effective Java》第三版 的第 13 条: 谨慎地改写clone。
finalize()
要覆盖 finalize(),所有你需要做的就是简单地声明它,而不需要 override 关键字:
class C {
protected fun finalize() {
// 终止化逻辑
}
}
根据 Java 的规则,finalize() 不能是 private 的。
从 Java 类继承
在 kotlin 中,类的超类中最多只能有一个 Java 类(以及按你所需的多个 Java 接口)。
访问静态成员
Java 类的静态成员会形成该类的“伴生对象”。我们无法将这样的“伴生对象”作为值来传递, 但可以显式访问其成员,例如:
if (Character.isLetter(a)) { …… }
要访问已映射到 Kotlin 类型的 Java 类型的静态成员,请使用 Java 类型的完整限定名:
java.lang.Integer.bitCount(foo)。
Java 反射
Java 反射适用于 Kotlin 类,反之亦然。如上所述,你可以使用 instance::class.java, ClassName::class.java 或者 instance.javaClass 通过 java.lang.Class 来进入 Java 反射。
其他支持的情况包括为一个 Kotlin 属性获取一个 Java 的 getter/setter 方法或者幕后字段、为一个 Java 字段获取一个 KProperty、为一个 KFunction 获取一个 Java 方法或者构造函数,反之亦然。
SAM 转换
就像 Java 8 一样,Kotlin 支持 SAM 转换。这意味着 Kotlin 函数字面值可以被自动的转换成只有一个非默认方法的 Java 接口的实现,只要这个方法的参数类型能够与这个 Kotlin 函数的参数类型相匹配。
你可以这样创建 SAM 接口的实例:
val runnable = Runnable { println("This runs in a runnable") }
……以及在方法调用中:
val executor = ThreadPoolExecutor()
// Java 签名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }
如果 Java 类有多个接受函数式接口的方法,那么可以通过使用将 lambda 表达式转换为特定的 SAM 类型的适配器函数来选择需要调用的方法。这些适配器函数也会按需由编译器生成:
executor.execute(Runnable { println("This runs in a thread pool") })
请注意,SAM 转换只适用于接口,而不适用于抽象类,即使这些抽象类也只有一个抽象方法。
还要注意,此功能只适用于 Java 互操作;因为 Kotlin 具有合适的函数类型,所以不需要将函数自动转换为 Kotlin 接口的实现,因此不受支持。
在 Kotlin 中使用 JNI
要声明一个在本地(C 或 C++)代码中实现的函数,你需要使用 external 修饰符来标记它:
external fun foo(x: Int): Double
其余的过程与 Java 中的工作方式完全相同。
Java 中调用 Kotlin
Java 可以轻松调用 Kotlin 代码。
属性
Kotlin 属性会编译成以下 Java 元素:
一个 getter 方法,名称通过加前缀 get 算出;
一个 setter 方法,名称通过加前缀 set 算出(只适用于 var 属性);
一个私有字段,与属性名称相同(仅适用于具有幕后字段的属性)。
例如,var firstName: String 编译成以下 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 类的静态方法。
// example.kt
package demo
class Foo
fun bar() { …… }
// Java
new demo.Foo();
demo.ExampleKt.bar();
可以使用 @JvmName 注解修改生成的 Java 类的类名:
@file:JvmName("DemoUtils")
package demo
class Foo
fun bar() { ... }
// Java
new demo.Foo();
demo.DemoUtils.bar();
如果多个文件中生成了相同的 Java 类名(包名相同并且类名相同或者有相同的 @JvmName 注解)通常是错误的。然而,编译器能够生成一个单一的 Java 外观类,它具有指定的名称且包含来自所有文件中具有该名称的所有声明。 要启用生成这样的外观,请在所有相关文件中使用 @JvmMultifileClass 注解。
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun foo() { ... }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun bar() { ... }
// Java
demo.Utils.foo();
demo.Utils.bar();
实例字段
如果需要在 Java 中将 Kotlin 属性作为字段暴露,那就需要使用 @JvmField 注解对其标注。 该字段将具有与底层属性相同的可见性。如果一个属性有幕后字段(backing field)、非私有、没有 open /override 或者 const 修饰符并且不是被委托的属性,那么你可以用 @JvmField 注解该属性。
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
Singleton.provider = new Provider();
// 在 Singleton 类中的 public static 非-final 字段用 const 标注的(在类中以及在顶层的)属性在 Java 中会成为静态字段:
// 文件 example.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 将包级函数表示为静态方法。 Kotlin 还可以为命名对象或伴生对象中定义的函数生成静态方法,如果你将这些函数标注为 @JvmStatic 的话。 如果你使用该注解,编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。 例如:
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 中的 public。internal 类的成员会通过名字修饰,使其更难以在 Java 中意外使用到,并且根据 Kotlin 规则使其允许重载相同签名的成员而互不可见;
public 保持 public。
KClass
有时你需要调用有 KClass 类型参数的 Kotlin 方法。 因为没有从 Class 到 KClass 的自动转换,所以你必须通过调用 Class.kotlin 扩展属性的等价形式来手动进行转换:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
用 @JvmName 解决签名冲突
有时我们想让一个 Kotlin 中的命名函数在字节码中有另外一个 JVM 名称。 最突出的例子是由于类型擦除引发的:
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>
这两个函数不能同时定义,因为它们的 JVM 签名是一样的:filterValid(Ljava/util/List;)Ljava/util/List;。 如果我们真的希望它们在 Kotlin 中用相同名称,我们需要用 @JvmName 去标注其中的一个(或两个),并指定不同的名称作为参数:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>
在 Kotlin 中它们可以用相同的名称 filterValid 来访问,而在 Java 中,它们分别是 filterValid 和 filterValidInt。
同样的技巧也适用于属性 x 和函数 getX() 共存:
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10
如需在没有显式实现 getter 与 setter 的情况下更改属性生成的访问器方法的名称,可以使用 @get:JvmName 与 @set:JvmName:
@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23
生成重载
通常,如果你写一个有默认参数值的 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 函数:
// example.kt
package demo
fun foo() {
throw IOException()
}
然后我们想要在 Java 中调用它并捕捉这个异常:
// Java
try {
demo.Example.foo();
}
catch (IOException e) { // 错误:foo() 未在 throws 列表中声明 IOException
// ……
}
因为 foo() 没有声明 IOException,我们从 Java 编译器得到了一个报错消息。 为了解决这个问题,要在 Kotlin 中使用 @Throws 注解。
@Throws(IOException::class)
fun foo() {
throw IOException()
}
型变的泛型
当 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() { …… }
注解 | 说明 |
---|---|
@JvmName | 设置在java中的名字 |
@JvmField | 如果需要在 Java 中将 Kotlin 属性作为字段暴露,那就需要使用 @JvmField 注解对其标注 |
@JvmStatic | Kotlin 还可以为命名对象或伴生对象中定义的函数生成静态方法,如果你将这些函数标注为@JvmStatic 的话 |
@JvmOverloads | 如果你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见,如果希望向 Java 调用者暴露多个重载,可以使用@JvmOverloads注解 |
@Throws | 我们要在 Kotlin 中使用 @Throws 注解,抛出异常,java才能捕获。 |
最后
以上就是害怕奇异果为你收集整理的kotlin第八天:异常、java和kotlin互操作异常java 和kotlin互操作的全部内容,希望文章能够帮你解决kotlin第八天:异常、java和kotlin互操作异常java 和kotlin互操作所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复