Kotlin和Java是有互操做性的(Interoperability). Kotlin和Java代码能够互相调用.html
为何在一个项目里这两种语言会同时存在呢?java
由于Java中的全部引用都是可能为null的. Java声明的类型在Kotlin中被称为platform types
.安全
从Java中传过来的引用, 能够赋值给Kotlin的非空类型, 不会有编译错误, 可是若是引用值为空, 会在运行时抛出异常.bash
举例, 在kotlin中调用Java的方法, 返回一个String:app
val stringOne = JavaUtils.getStringOne()
println(stringOne)
println(stringOne.length)
复制代码
若是Java方法返回null怎么办? 运行这段程序时先打出null, 再抛出NullPointerException
.jvm
若是程序是这样写的:ide
val stringOne: String = JavaUtils.getStringOne()
println(stringOne)
println(stringOne.length)
复制代码
说明Kotlin假设Java传回来的是一个非空值. 这段代码编译时不会报错, 可是运行时第一行就抛出IllegalStateException
.函数
若是这样写:测试
val stringOne: String? = JavaUtils.getStringOne()
println(stringOne)
println(stringOne?.length)
复制代码
终于利用上了Kotlin的空安全检查, 全部用到这个变量的地方都要加上?
, 若是不作检查编译时就会提示错误. 可是这样防护不免致使代码太啰嗦了, 可能处处都是?
.ui
好的实践是Java中的Public APIs(Non-primitive parameters, Field type, Return)都应该加上注解.
若是Java的类型上有关于null的注解, 就会直接表示为Kotlin中为不为null或者可为null的对应类型.
注解能够来自于各类包中, 好比JetBrains提供的: @Nullable
和@NotNull
.
好比:
@NotNull
public static String getStringOne() {
return "hello";
}
复制代码
这样Kotlin代码就知道传过来的确定是个非空值, 能够放心使用.
若是是@Nullable
, 编译器就会提示使用前作检查.
Kotlin中的关键字, 好比:
fun, in, is, object, typealias, typeof, val, var, when
复制代码
若是Java代码中用了这些关键字, 在Kotlin中调用该Java代码就要用`进行转义.
好比若是java中有一个名称为is
的方法, 在kotlin中想要调用:
foo.`is`(bar)
复制代码
可是, 首先须要考虑是否是名字起得很差, 若是能够更名(不是第三方代码), 优先考虑更名.
比较常见的一个使用情形是在写测试的时候, Mockito中的when
就须要转义:
Mockito.`when`(xxx.foo()).thenReturn(yyy)
复制代码
由于Mockito是一个Java的第三方库, 咱们无法改它.
另外一个解决办法是使用import alias, 给这个方法取个别名:
import org.mockito.Mockito.`when` as whenever
复制代码
这样在使用的时候就能够用whenever
来代替了when
了. import alias一般用来解决命名冲突的问题.
SAM: Single Abstract Method.
只要函数参数匹配, Kotlin的函数能够自动转换为Java的接口实现.
Convention: 能够作SAM转换的参数类型应该放在方法的最后, 这样看起来更舒服.
举例, 若是在Java中定义方法:
interface Operation {
int doCalculate(int left, int right);
}
public static int calculate(Operation operation, int firstNumber, int secondNumber) {
return operation.doCalculate(firstNumber, secondNumber);
}
复制代码
在Kotlin中调用的时候用SAM转换, 用一个lambda做为接口实现:
JavaUtils.calculate({ number1, number2 -> number1 + number2 }, 2, 3)
复制代码
这样虽然正确, 可是能够改进. 把Java方法定义中的参数位置交换一下, 把接口参数放在最后:
public static int calculate(int firstNumber, int secondNumber, Operation operation) {
return operation.doCalculate(firstNumber, secondNumber);
}
复制代码
在Kotlin中, 最后一个lambda参数能够提取到括号外面:
JavaUtils.calculate(2, 3) { number1, number2 -> number1 + number2 }
复制代码
这样看起来更好.
注意: SAM conversion只应用于java interop.
上面的例子, 若是接口和方法是在Kotlin中定义的:
interface Operation2 {
fun doCalculate(left: Int, right: Int): Int
}
fun calculate2(firstNumber: Int, secondNumber: Int, operation: Operation2): Int {
return operation.doCalculate(firstNumber, secondNumber)
}
复制代码
SAM conversions就不能用了, IDE会提示没法识别. 调用这个方法时, 第三个参数必须写成这种(匿名类的对象, 实现了接口):
calculate2(2, 3, object : Operation2 {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
复制代码
这是由于在Kotlin的世界里, 函数是第一公民.
若是把前面的方法参数改成function type:
fun calculate3(firstNumber: Int, secondNumber: Int, operation: (Int, Int) -> Int): Int {
return operation.invoke(firstNumber, secondNumber)
}
复制代码
就能够像以前SAM conversions似的使用:
calculate3(2, 3) { number1, number2 -> number1 + number2 }
复制代码
若是接口是在Java中定义, 可是接收参数的方法是Kotlin的方法:
fun calculate4(firstNumber: Int, secondNumber: Int, operation: JavaUtils.Operation): Int {
return operation.doCalculate(firstNumber, secondNumber)
}
复制代码
仍然是不能用SAM conversions, 由于这个方法仍然是能够接受函数类型的参数的. 在Kotlin中调用:
calculate4(2, 3, object : JavaUtils.Operation {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
复制代码
IDE会提示你简化为:
calculate4(2, 3, JavaUtils.Operation { left, right -> left + right })
复制代码
注意这里接口名称不能省略.
是否是感受有点晕, 我把上面提到的几个调用状况写在一块儿:
// java function, java interface parameter
private fun trySAM1() {
JavaUtils.calculate(2, 3) { number1, number2 -> number1 + number2 }
}
// kotlin function, kotlin interface parameter
private fun trySAM2() {
calculate2(2, 3, object : Operation2 {
override fun doCalculate(left: Int, right: Int): Int {
return left + right
}
})
}
// kotlin function, function type parameter
private fun trySAM3() {
calculate3(2, 3) { number1, number2 -> number1 + number2 }
}
// kotlin function, java interface parameter
private fun trySAM4() {
calculate4(2, 3, JavaUtils.Operation { left, right -> left + right })
}
复制代码
能够互相比较一下, 看看区别.
Java中的getter和setter在Kotlin中会表现为properties. 可是若是只有setter, 不会做为可见的property.
Kotlin中全部的异常都是unchecked的, 因此若是调用的Java代码有受检异常, kotlin并不会强迫你处理.
Java中返回void的方法在Kotlin中会变成Unit
.
java.lang.Object
会变成Any
, Any
中的不少方法都是扩展方法.
Java没有运算符重载, 可是Kotlin支持. (运算符重载容易存在过分使用的问题.)
Kotlin的属性会被编译成Java中的一个私有字段, 加上getter和setter方法.
若是想要做为一个字段, 能够加上@JvmField
注解.
若是在一个文件app.kt
中定义方法, 包名是org.example
, 会被编译成Java的静态方法, Java类的类名是org.example.AppKt
.
应用场景举例: 旧代码中有一个Java的辅助类, 包含静态方法:
public class Utils {
public static int distanceBetween(int point1, int point2) {
return point2 - point1;
}
}
复制代码
要把这个辅助类迁移到Kotlin代码, 能够新建一个Kotlin文件DistanceUtils.kt
, 直接写包级别的方法:
fun distanceBetween(point1: Int, point2: Int): Int {
return point2 - point1
}
复制代码
在Java中调用这个方法的时候:
DistanceUtilsKt.distanceBetween(7, 9);
复制代码
若是原先的Java代码中包含调用这个方法的地方太多, 又不想改全部的usage, 怎么办? -> 能够经过注解@file:JvmmName("xxx")
改变类名. 这样原先Java代码中调用的地方就避免了修改.
若是有两个文件指定了相同的JvmName, 编译会报错. 能够经过加上@file:JvmMultifileClass
来解决. 这样多个Kotlin文件中定义的辅助方法对于Java来讲会统一到同一个类中.
如你须要把kotlin的property做为字段暴露出来, 能够加上@JvmField
注解.
适用的property: 有backing field, 没有这些修饰符: private
, open
, override
, const
, 也不是代理属性.
lateinit的属性会自动暴露为fields, 可见性和属性的setter一致.
Kotlin的data class会自动生成getter/setter. 若是加上@JvmField
, 会直接暴露这些字段.
能够经过@get:JvmName("xxx")
和@set:JvmName("xxx")
来定制getter和setter的名字.
Kotlin在有名字的object或者companion object中声明的属性, 将会编译成静态字段.
一般这些字段是private的, 不过也能够经过如下几种方式暴露:
@JvmField
.lateinit
.const
.前面提过, Kotlin包级别的方法会被编译成静态方法.
在object或companion object中声明的方法, 默认是类中的实例方法. 好比:
class StaticMethodsDemoClass {
companion object {
fun sayHello() {
println("hello")
}
}
}
object SingletonObject {
fun sayWorld() {
println("world")
}
}
复制代码
在Java中调用的时候:
StaticMethodsDemoClass.Companion.sayHello();
SingletonObject.INSTANCE.sayWorld();
复制代码
若是给object或companion object中的方法加上@JvmStatic
, 会生成一个静态方法和一个实例方法. 调用的时候就能够省略掉中间的Companion
或INSTANCE
关键字, 以类名直接调用静态方法.
好比:
class StaticMethodsDemoClass {
companion object {
fun sayHello() {
println("hello")
}
@JvmStatic
fun sayHelloStatic() {
println("hello")
}
}
}
复制代码
调用的时候:
StaticMethodsDemoClass.Companion.sayHello();
//StaticMethodsDemoClass.sayHello(); // error
StaticMethodsDemoClass.Companion.sayHelloStatic(); // ok, but not necessary
StaticMethodsDemoClass.sayHelloStatic();
复制代码
@JvmStatic
也能够用于属性, 就会有静态版本的getter和setter方法.
Kotlin方法支持默认参数, 在Java中只有所有参数的方法签名才是可见的. 若是你但愿对Java暴露多个方法重载, 要给方法加上@JvmOverloads
. 好比在Kotlin中写一个自定义View的构造函数:
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}
复制代码
还能够利用 @JvmName
来给方法重命名. 由于在Kotlin中是扩展方法, 在Java中只是一个静态方法, 名字可能不够直观.
Kotlin没有checked exceptions. 若是想在Java中调用一个Kotlin方法, 并包一个try-catch, 会报错说没有抛出这个异常.
能够在Kotlin方法中加上注解, 好比@Throws(IOException::class)
.
Kotlin方法的参数名, 在生成代码中会做为一个字符串出现, 从而不会被混淆, 有可能会泄漏. 因此不建议放敏感信息到参数名中.
相似的还有字段名, 扩展方法名.
能够在proguard中加上
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkParameterIsNotNull(...);
public static void throwUninitializedPropertyAccessException(...);
}
复制代码
来移除这些代码. 可是建议在测试环境中仍然保留这些代码, 以便有错误发生的时候可以快速发现.
Code -> Convert Java File to Kotlin File
.
若是粘贴Java代码到.kt文件, IDE会自动将所粘贴代码转换为Kotlin代码.
在IDE里面能够显示Kotlin Bytecode, 而后decompile, 显示java代码.
Tools -> Kotlin -> Show Kotlin Bytecode -> Decompile
.