Kotlin在设计的时候就考虑到了和Java的交互,在现有的情况下Kotlin本身就可以调用java代码,并且Kotlin代码也可以在Java中正常使用,在本篇中,我们将讨论在Kotlin中调用java代码的一些细节。
完美的使用java代码没有任何问题:
import java.util.*
fun demo(source: List) {
val list = ArrayList()
// 'for'-loops work for Java collections:
for (item in source) {
list.add(item)
}
// Operator conventions work as well:
for (i in 0..source.size() - 1) {
list[i] = source[i] // get and set are called
}
}
getter和setter方法都遵从java习惯(方法名以get开头的无参方法和方法名以set开头的只有一个参数的方法),这些都还出现在kotlin的属性中,例如:
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}
}
==注意:如果java类中只有setter,那么在kotlin就不能作为一个属性,因为kotlin不支持只有setter的属性==
如果在Java中是返回void,那么在kotlin中就是返回Unit,如果有任何改变,某些改变还会反应在返回值中,kotlin编译还是把它赋值到调用处,因为值本身就是预先知道的.
有一些kotlin中的关键字如in,object,is等,在java中是无效的,如果java库中应用到了kotlin的关键字,你可以通过使用后撇号(`)来包裹关键字后再继续使用
foo.`is`(bar)
在Java中的所有引用都可以为null,Kotlin中严格要求所有对象都是零风险的空安全就是来自java的这一思想,在kotlin中处理java中的声明类型被称为平台类型,空检查就是这么一种类型,所以它们的安全保障和在java中是一样的。
请思考下面的例子:
val list = ArrayList() // non-null (constructor result)
list.add("Item")
val size = list.size() // non-null (primitive int)
val item = list[0] // platform type inferred (ordinary Java object)
当我们从平台类型的变量中调用方法的时候,kotlin在运行时并没有标识空异常,但在运行时却有可能调用失败,因为空指针或断言会让kotlin产生防止空传播。
item.substring(1) // allowed, may throw an exception if item == null
平台类型是不可表示的,意思就是不能用语言明确的把它们写下来,当一个平台类型的值赋值给kotlin变量时,我们可以依赖类型推断(每个平台类型都可以推断成变量,像上面那个例子的item),或者我们可以选择我们期望的类型(可为空或非空类型都可以):
val nullable: String? = item // allowed, always works
val notNull: String = item // allowed, may fail at runtime
如果我们选择非空类型,编译器会明确执行一些断言,这可以避免kotlin的非空变量变成空引用,当我们传递平台值给kotlin的时,希望是非空值时,断言就会执行,整体上,编译器会尽最大努力的防止空传递经过程序。
前面章节中有提到,在程序中不能明确指定平台类型,因此在语言中没有想关它们的语法,然而,编译器和IDE有时候还是需要显示它们(在错误信息、参数信息等),因此我们有一个助记符:
- T! 意思就是’T’ 或者’T?’
- (Mutable)Collection! 意思就是Java集合中的T可能会变,有可能为空
- Array<(out) T>! 意思就是Java数组中的T(或者是T的子类型),有可能为空
java中可为空的注解不会出现在平台类型中,但事实上Kotlin都有可为空的类型或非空类型,编译器支持几种可为空的注解,包含以下:
- JetBrains(@Nullable和@NotNull,位于org.jetbrains.annotations包下)
- 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)
可以在Kotlin编译器源码中找到全部.
Kotlin对待Java的类型有些特别,有些类型没有直接从Java转过来,但还是有相对应的映射类型,这些类型会在编译时就会匹配,在运行时就会保持不变,Java的基本类型也有对应的Kotlin类型相对应:
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.Enum | kotlin.Enum! |
java.lang.Comparable | kotlin.Comparable! |
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 类型 | Kotlin 类型 |
---|---|
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? |
==注意:基本类型的包装类通常被用来平台类型的映射,例如List
Kotlin的泛型跟Java的泛型有一点点的不同,当把Java类型导入到Kotlin的时候我们会做一些转化:
- Java的通配符会转为类型推断
- Foo
if (a is List) // 错误: cannot check if it is really a List of Ints
// but
if (a is List<*>) // OK: no guarantees about the contents of the list
在Kotlin中的数组是不变的,不像Java,这意味着在Kotlin中不要求我们把Array赋值给Array,如果强制这么做,可能会导致运行失败.在kotlin中是禁止把子类的数组转化为超类的数组,但在java这种方式是可以的.
在Java平台上,基本类型的数组是为了避免自动装箱和自动拆箱的消耗,虽然kotlin隐藏了具体细节,但还是需要暴露方法给java代码调用,每个基本类型数组都有特殊的类(如IntArray,DoubleArray,CharArray等)去处理这种情况,他们没有相关的Array类,并且需要在编译的时候降为基本类型以达到最好性能。
假设这里有一个java方法接受数组的索引:
public class JavaArrayExample {
public void removeIndices(int[] indices) {
// code here...
}
}
在Kotlin中你传递基本类型数组,你可以这样做:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array) // passes int[] to method
当编译成JVM字节码的识货,编译器的优化就会让数组看起来不太像之前介绍的那样:
val array = arrayOf(1, 2, 3, 4)
array[x] = array[x] * 2 // no actual calls to get() and set() generated
for (x in array) { // no iterator created
print(x)
}
如果我们找到一个指数,它也不会像前面介绍的那样:
for (i in array.indices) { // no iterator created
array[i] += 2
}
最终,内部的类型检查(in-checks)也没有像前面那样检查:
if (i in array.indices) { // same as (i >= 0 && i < array.size)
print(array[i])
}
java的方法有时候会用到可变参数
public class JavaArrayExample {
public void removeIndices(int... indices) {
// code here...
}
}
有另外一种情况,你可能需要范围操作符来传递IntArray:
val javaObj = JavaArray()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
这是目前不可能通过一个声明为可变参数的中间方法
因为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就要使用拓展函数.
在【Effective Java】一书第69章中,友好的提示并发时需要用到wait()和notify(),然而,这些方法在Any类型的引用中是无效的,如果你真的需要调用它们,你可以把它转型为java.lang.Object对象来使用
(foo as java.lang.Object).wait()
如果要检索Object的java类,要用到class引用的java 拓展属性
val fooClass=foo::class.java
这个代码就是使用前面提到的绑定类引用一节中所说的,它从版本1.1开始支持,当然你也可以使用javaClass的拓展属性
val fooClass=foo.javaClass
如果要覆写clone()方法,那么你的类就得继承kotlin.Cloneable:
class Example : Cloneable {
override fun clone(): Any { ... }
}
如果需要覆写finalize(),你只需要简单的声明,并不需要使用override关键字:
class C {
protected fun finalize() {
// finalization logic
}
}
根据Java的开发原则,finalize()必须不可以被private修饰
Java类的静态成员是这些类的’同级对象’,虽然我们不可以把这些’同级对象’当成值来使用,但是我们可以明确的访问这些成员,例如:
if (Character.isLetter(a)) {
// ...
}
Java的反射可以工作再Kotlin类中,反之亦然,在前面提到的那样,你可以使用instance::class.java,ClassName::class.java或instance.javaClass来获得java反射中的java.lang.Class.
其他情况就需要java中有getter何setter方法或有kotlin中的隐性属性,有java字段的KProperty,java方法,或KFunction的构造方法
如果要声明一个实现本地方法(C/C++),你可以使用external来修饰:
external fun foo(x: Int): Double
其他的相关实现就和java中一样应用