[关闭]
@946898963 2020-07-09T08:17:06.000000Z 字数 7189 阅读 733

条条小小Kotlin与Java互调

Kotlin


Kotlin调Java

a. 当Java中的方法/字段的名称是Kotlin中的关键字时(如: in is object等), 我们可以通过使用反引号`来转义这个关键字

  1. foo.`is`(bar)

b. Java 中的 int 和 Integer 只会对应 Kotlin 中 Int 这一种数据类型
Java 中的 int[] 会对应 Kotlin 中的 IntArray
Java 中的 Integer[] 会对应 Kotlin 中的 Array
其他的基本类型数组定义也是和上面一样的规则

  1. // Java
  2. public void method(int[] intArray) {}
  3. // Kotlin
  4. method(IntArray(10))

c. Kotlin 调用 Java 中的变长参数的方法时, 可以使用 * 来传递/扩展数组

  1. // Java
  2. public void method(int... params) {}
  3. // Kotlin
  4. val intArray = IntArray(10)
  5. method(*intArray) // 传递数组
  6. method(*intArray, 1, 2, 3) // 扩展数组

d. SAM接口的转换

总结: Kotlin中只有调用以Java中定义的SAM接口为参数的方法(不论是Java中还是Kotlin 中定义的)时, 编译器才会SAM转换, 从而我们才可以使用Lambda来传入方法, 否则都要实现 匿名内部类来传入方法

  1. // Java
  2. public interface InterfaceJAVA {
  3. void print(String text);
  4. }
  5. public void printJavaFromJava(InterfaceJAVA interfaceJAVA) {
  6. interfaceJAVA.print("printJavaFromJava");
  7. }
  8. public void printKotlinFromJava(InterfaceKotlin interfaceKotlin) {
  9. interfaceKotlin.print("printKotlinFromJava");
  10. }
  11. // Kotlin
  12. interface InterfaceKotlin {
  13. fun print(text: String)
  14. }
  15. fun printJavaFromKotlin(interfaceJava: InterfaceJAVA) {
  16. interfaceJava.print("printJavaFromKotlin")
  17. }
  18. fun printKotlinFromKotlin(interfaceKotlin: InterfaceKotlin) {
  19. interfaceKotlin.print("printKotlinFromKotlin")
  20. }
  21. // 调用Java中定义的以Java中定义的SAM接口为参数的方法
  22. printJavaFromJava { println(it) }
  23. // 调用Java中定义的以Kotlin中定义的SAM接口为参数的方法
  24. printKotlinFromJava(object : InterfaceKotlin {
  25. override fun print(text: String) {
  26. println(text)
  27. }
  28. })
  29. // 调用Kotlin中定义的以Java中定义的SAM接口为参数的方法
  30. printJavaFromKotlin(InterfaceJAVA { println(it) })
  31. // 调用Kotlin中定义的以Kotlin中定义的SAM接口为参数的方法
  32. printKotlinFromKotlin(object : InterfaceKotlin {
  33. override fun print(text: String) {
  34. println(text)
  35. }

e. Kotlin中所有的异常都是非受检异常, 所以当我们在Kotlin中调用一个Java中的方法时, 需要自己判断是否需要try catch
Kotlin认为Java中的受检异常机制在大型项目中并不能够很好的增加项目的质量, 而且还会降低代码的可读性和复用性, 也会增加开发成本, 所以就把受检异常这个机制给去除了....

  1. // Java
  2. public void methodWithException(int i) throws IOException {
  3. System.out.println("methodWithException start");
  4. if (i == 1) {
  5. throw new IOException();
  6. }
  7. System.out.println("methodWithException end");
  8. }
  9. // Java中调用methodWithException方法时, 必须强制处理IOException这个异常,
  10. // 因为IOException是一个受检异常
  11. try {
  12. methodWithException(0);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. // Kotlin
  17. // Kotlin中所有异常都不需要强制处理, 因此我们不需要强制的在调用methodWithException
  18. // 的地方去强制的try catch IOException异常
  19. methodWithException(0)

Java调Kotlin

a. Java中调用Kotlin中定义的top-level declarations(顶级的类/方法/变量/常量等)时, 默认使用[Kotlin文件名]KT.[declarations name]的方式调用, 也可以在KT文件中使用JvmName注解来重新定义顶层变量的文件名

  1. // Test.Kt
  2. package org.example
  3. fun getTime() { /*...*/ }
  4. // Java
  5. org.example.TestKt.getTime();
  6. // Test.Kt
  7. @file:JvmName("DemoUtils")
  8. package org.example
  9. fun getTime() { /*...*/ }
  10. // Java
  11. org.example.DemoUtils.getTime();

b. 如果想把多个KT文件中的top-level declarations合并, 可以使用JvmMultifileClass注解来合并多个Kt文件

  1. // oldutils.kt
  2. @file:JvmName("Utils")
  3. @file:JvmMultifileClass
  4. package org.example
  5. fun getTime() { /*...*/ }
  6. // newutils.kt
  7. @file:JvmName("Utils")
  8. @file:JvmMultifileClass
  9. package org.example
  10. fun getDate() { /*...*/ }
  11. // Java
  12. org.example.Utils.getTime();
  13. org.example.Utils.getDate();

c. Java调用Kotlin的属性时如果不想通过getter/setter调用, 而是使用[objectName].[field]的方式调用, 则可以使用JvmField注解
前提是这个字段 1. 不是Private 2. 没有open override const修饰符 3. 不是委托属性

  1. // Kotlin
  2. class Person {
  3. @JvmField var name: String = "石硕"
  4. var height: Int = 190
  5. lateinit var brother: Person
  6. }
  7. // Java
  8. Person person = new Person();
  9. person.name = "猜猜我是谁" // 加了@JvmField注解后就可以直接操作field
  10. person.setHeight(170) // 正常情况下只能使用setter/getter来操作field
  11. person.brother = new Person() // 使用lateinit修饰的field也可以在Java中直接操作

d. Kotlin中的静态属性需要定义在companion object / 自定义的object中.

Java中调用Kotlin声明的静态属性时, 默认使用如下的两种方式:

如果想使用[ClassName].[fieldName] / [ObjectName].[fieldName] 的方式调用Kotlin中定 义的静态变量, 可以使用如下的三种方式:

  1. // Kotlin
  2. class TestStaticField {
  3. companion object {
  4. const val TEXT = "Text."
  5. @JvmField
  6. val TEXT_WITH_JVM_FIELD = "Text with JvmField"
  7. lateinit var TEXT_WITH_LATE_INIT: String
  8. val TEXT_NORMAL = "Text normal"
  9. }
  10. }
  11. // Java
  12. // 调用Const修饰静态属性
  13. System.out.println(TestStaticField.TEXT);
  14. // 调用@JvmField修饰的静态属性
  15. System.out.println(TestStaticField.TEXT_WITH_JVM_FIELD);
  16. // 调用latainit修饰的静态属性
  17. TestStaticField.TEXT_WITH_LATE_INIT = "Text with lateinit";
  18. // 默认的方式使用静态属性
  19. System.out.println(TestStaticField.Companion.getTEXT_NORMAL());

e. Kotlin中的静态方法与静态属性一样需要定义在companion object / 自定义的object中.

Java中调用Kotlin声明的静态方法时, 默认使用如下的两种方式:

如果想使用[ClassName].[methodName] / [ObjectName].[methodName] 的方式调用Kotlin中定义的静态方法, 可以使用@JvmStatic注解

  1. // Kotlin
  2. class TestStaticMethod {
  3. companion object {
  4. @JvmStatic
  5. fun methodWithJvmStatic() {}
  6. fun methodWithoutJvmStatic() {}
  7. }
  8. }
  9. // Java
  10. // 调用@JvmStatic修饰的方法
  11. TestStaticMethod.Companion.methodWithoutJvmStatic();
  12. // 调用未使用@JvmStatic修饰的方法
  13. TestStaticMethod.methodWithJvmStatic();

f. kotlin 的调用规范与 Java 不同, 我们可以通过@JvmName 来设计名称, 使其符合这两种语言的惯例或与各自的标准库命名保持一致。

  1. // Utils.Kt
  2. @JvmName("isStringBlankOrEmpty")
  3. fun String?.isBlankOrEmpty(): Boolean {
  4. return this != null && this.isNotEmpty()
  5. }
  6. // Kotlin
  7. "123".isBlankOrEmpty()
  8. // Java
  9. UtilsKt.isStringBlankOrEmpty("123");

g. 当Kotlin中field的getter/setter与方法重名时, 我们也可以使用@JvmName 来对getter/setter重新命名

  1. // Kotlin
  2. class Person {
  3. val height: Int = 190
  4. val name: String = "石硕"
  5. @JvmName("getNameProp")
  6. get
  7. @get:JvmName("getCountryProp")
  8. @set:JvmName("setCountryProp")
  9. val country: String = "China"
  10. fun getName() = "猜猜我是谁"
  11. }
  12. // Kotlin
  13. Person().name
  14. Person().getName()
  15. // Java
  16. Person person = new Person();
  17. person.getNameProp();
  18. person.getName();

h. Java在调用Kotlin使用默认参数的方法时, 如果方法没有使用@JvmOverloads时, 则该方法只会被编译成一个拥有全部参数的方法来供Java调用.

  1. // Kotlin
  2. fun methodWithDefault(paramsA: Int = 0, paramsB: String = "123", paramsC: Boolean = true) {}
  3. @JvmOverloads
  4. fun methodWithDefaultAndJvmOverloads(paramsA: Int = 0, paramsB: String = "123", paramsC: Boolean = true) {}
  5. // Java
  6. methodWithDefault(1); // 编译错误
  7. methodWithDefault(1, "456", false); // 编译成功
  8. methodWithDefaultAndJvmOverloads(1) // 编译成功

i. Kotlin中所有的异常都是非受检异常, 我们可以通过@Throws注解来通知Java调用该方法时, 需要处理的受检异常是什么.

  1. // Kotlin
  2. fun writeToFile() {
  3. /*...*/
  4. throw IOException()
  5. }
  6. @Throws(IOException::class)
  7. fun writeToFileWithThrows() {
  8. /*...*/
  9. throw IOException()
  10. }
  11. // Java
  12. try {
  13. writeToFile();
  14. }
  15. catch (IOException e) { // 编译错误 编译器获取不到受检异常
  16. e.printStackTrace();
  17. }
  18. try {
  19. writeToFileWithThrows();
  20. }
  21. catch (IOException e) { // 编译成功
  22. e.printStackTrace();
  23. }

j. 当Kotlin的方法中定义的参数为非空类型时, 在方法开始时会使用Intrinsics.checkParameterIsNotNull()方法, 去检查该参数是否为空, 如果为null则会抛出异常, 导致应用Crash.
因此Java在调用Kotlin中如果在Kotlin中定义的方法时, 一定要判断好是否可以传null

  1. // Intrinsics.java
  2. public static void checkParameterIsNotNull(Object value, String paramName) {
  3. if (value == null) {
  4. throwParameterIsNullException(paramName);
  5. }
  6. }
  7. // Kotlin
  8. fun methodA(paramsA:Int?)
  9. fun methodB(paramsA:Int)
  10. // Java
  11. methodA(null) // 正常运行
  12. methodB(null) // 运行时Crash

k. 当从Java中调用Kotlin中含有函数类型的方法时, 如果此函数类型的返回值是Unit, 则在Java的lambda的最后需要返回return Unit.INSTANCE;

  1. // Kotlin
  2. fun printMethod(callback: (str: String) -> Unit) {
  3. callback("Hello World")
  4. }
  5. printMethod { println(it) } // Kotlin中不需要返回Unit.INSTANCE
  6. // Java
  7. printMethod(text -> {
  8. System.out.println(text);
  9. return Unit.INSTANCE; // Java中需要返回Unit.INSTANCE
  10. });

如果想避免这种这种情况只能选择不使用Kotlin的函数类型, 而使用SAM接口. 但是这样做就会使 Kotlin在调用时无法使用lambda, 只能传递使用匿名内部类

  1. // Kotlin
  2. interface InterfaceKotlin {
  3. fun print(text: String)
  4. }
  5. fun printMethod(callback: InterfaceKotlin) {
  6. callback.print("Hello World")
  7. }
  8. // Kotlin只能使用匿名内部类调用
  9. printMethod(object : InterfaceKotlin {
  10. override fun print(text: String) {
  11. println(text)
  12. }
  13. })
  14. // Java中不需要单独返回Unit.INSTANCE
  15. printMethod(text -> {
  16. System.out.println(text);
  17. });

官方说目前没有好的解决方案, 建议在Kotlin的方法中使用函数类型参数, 而不是选择SAM接口类型.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注