Kotlin与Java混合开发(3)✔️Java调用Kotlin

2019-06-24  本文已影响0人  狼性代码人
  • 访问 Kotlin 属性

  • 访问包级别成员

    • 使用 文件名 访问
    • 使用 @file:JvmName("自定义名") 访问
  • 实例字段、静态字段和静态函数

    • 实例字段
    • 静态字段
    • 静态函数
  • 可见性

  • 生成重载函数

  • 异常检查

  Java 调用 Kotlin 要比 Kotlin 调用 Java 要麻烦一些,但还是比较容易实现的。

一、访问 Kotlin 属性

  Kotlin 的一个属性对应 Java 中的一个私有字段、一个 setter 函数 和 一个 getter 函数,如果是只读属性则没有 setter 函数。那么 Java 访问 Kotlin 的属性是通过这些 getter 函数 和 setter 函数。

// User.kt
data class User(val name: String, var password: String)
// MainJava.java
public class MainJava {

    public static void main(String[] args) {
        User user = new User("小三", "123456");
        System.out.println(user.getName());
        System.out.println(user.getPassword());
        user.setPassword("abcdef");
        System.out.println(user.getPassword());
    }
}
// 运行结果
小三
123456
abcdef
Process finished with exit code 0

  var 声明的属性会生成 gettersetter 两个函数,但 val 声明的属性是只读的,所以只会生成 getter 一个函数。

二、访问包级别成员

  在同一个 Kotlin 文件中,那些 顶层属性和函数 (包括顶层扩展属性和函数) 都不隶属于某个类,但它们 隶属于该 Kotlin 文件中定义的包。在 Java 中访问它们时,把它们当成静态成员。

// 1️⃣ 代码文件:kot/kotlin_module/src/main/java/cn/ak/kotmodule/kot/topper.kotlin.kt
// 1️⃣ 文件名为:topper.kotlin.kt

package cn.ak.kotmodule.kot

// 顶层函数
fun rectangleArea(width: Double, height: Double): Double = width * height

// 顶层属性
val area = 100.0

// 顶层扩展函数
fun User.printInfo() = println("{name=$name, password=$password}")

  上述是一个名为 topper.kotlin.kt 的 kotlin 源代码文件,见第1️⃣行注释说明。文件 topper.kotlin.kt 中声明了一个 顶层函数、一个 顶层属性 和 一个 User扩展函数topper.kotlin.kt 文件编译后会生成一个 Topper_kotlinKt.class 文件,因为 点(.) 字符不能构成 Java 类名,编译器会将其 替换下划线(_),所以在 Java 中访问 topper.kotlin.kt 对应的类名是 Topper_kotlinKt,见下面调用案例的第2️⃣~4️⃣行代码。

// MainJava.java
public class MainJava {

    public static void main(String[] args) {
        // 访问顶层函数
        Double area = Topper_kotlinKt.rectangleArea(100, 50); // 2️⃣
        System.out.println(area);
        // 访问顶层属性
        System.out.println(Topper_kotlinKt.getArea()); // 3️⃣
        // 访问扩展函数
        User user = new User("小三", "Lawrence");
        Topper_kotlinKt.printInfo(user); // 4️⃣
    }
}

  如果你觉得上面使用 文件名 来调用函数和属性不够友好,但还不想修改 Kotlin 源文件名,那么可以在 Kotlin 源文件中使用 @JvmName 注解,指定生成的文件名,如下面代码第1️⃣行。

@file:JvmName("ExKotlin")  //1️⃣

package cn.ak.kotmodule.kot

// 顶层函数
fun rectangleArea(width: Double, height: Double): Double = width * height

// 顶层属性
val area = 100.0

// 顶层扩展函数
fun User.printInfo() = println("{name=$name, password=$password}")

  注意:@JvmName 注解必须放在 文件的第一行,否则会报编译错误。

// MainJava.java
public class MainJava {

    public static void main(String[] args) {
        // 访问顶层函数
        Double area = ExKotlin.rectangleArea(100, 50);
        System.out.println(area);
        // 访问顶层属性
        System.out.println(ExKotlin.getArea());
        // 访问扩展函数
        User user = new User("小三", "Lawrence");
        ExKotlin.printInfo(user);
    }
}

三、实例字段、静态字段和静态函数

  Java 语言中所有的变量和函数都被封装到一个类中,类中包括实例函数(实例方法)、实例属性(成员变量)、静态属性(静态成员变量) 和 静态函数(静态方法),即:Java 中所有东西都放在类中 class 类名 {...}

  如果需要以 Java 实例成员变量形式(即:实例名.成员变量)访问 Kotlin 中的属性,则需要在该属性前加 @JvmField 注释,表明该属性被当作 Java 中的成员变量使用,访问可见性相同。另外,延迟初始化(lateinit) 属性在 Java 中被当作成员变量使用,访问可见性相同。

// kotlin
class Person {
    // 姓名
    @JvmField  // 1️⃣
    val name = "小三"
    // 年龄
    var age = 20
    // 生日
    lateinit var birthDate: Date // 2️⃣
}
// java
public class MainJava {

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.name);        // 3️⃣
        System.out.println(person.getAge());    // 看不到属性person.age
        System.out.println(person.birthDate);   // 4️⃣
    }
}

  kotlin 源码中第1️⃣行使用 @JvmField 注释声明 name 属性,所以代码第3️⃣行可以直接访问。kotlin 源码第2️⃣行 birthDate 声明为延时属性,而延时属性在 Java 源码中也可以直接访问。

  如果需要以 Java 静态成员变量形式(即:类名.静态成员变量) 访问 Kotlin 中的属性,可以有两种实现方法:

  (1) 属性声明为顶层属性,Java 中将所有的顶层成员(属性和函数)都认为是静态的。

// kotlin
@file:JvmName("ExKotlin") 

// 顶层属性
@JvmField
val area = 100.0

const val MAX_COUNT = 100

  (2) 在 Kotlin 的声明对象和伴生对象中定义属性,这些属性需要使用 @JvmField 注解、lateinitconst 来修饰。什么是伴生对象?click me !

// kotlin
object Singleton {
    @JvmField
    val x = 10

    const val y = 100

    lateinit var birthDate: Date
}

class Size {
    var w = defaultHeight
    var h = 0

    companion object {
        @JvmField
        var defaultHeight = 100

        const val defaultWith = 100

        lateinit var defaultSize: Size
    }
}

调用上述示例代码:

// java
public class MainJava {

    public static void main(String[] args) {
        System.out.println("--------------------");
        System.out.println(ExKotlin.area);
        System.out.println(ExKotlin.MAX_COUNT);

        System.out.println("--------------------");
        System.out.println(Singleton.birthDate);
        System.out.println(Singleton.x);
        System.out.println(Singleton.y);

        System.out.println("--------------------");
        System.out.println(Size.defaultWith);
        System.out.println(Size.defaultHeight);
        System.out.println(Size.defaultSize);
    }
}
// 运行结果:
--------------------
100.0
100
--------------------
null
10
100
--------------------
100
100
null

Process finished with exit code 0

  如果需要以 Java 静态方法形式(即:类名.方法名) 访问 Kotlin 中的函数,可以有两种实现方法:

  (1) 函数声明为顶层函数,Java 中将所有 kotlin中的顶层函数(属性和函数)都认为是静态的。

// kotlin
@file:JvmName("ExKotlin") 

// 顶层函数
fun rectangleArea(width: Double, height: Double): Double = width * height

  (2) 在 Kotlin 的声明对象(object)伴生对象(companion object) 中定义函数,这些函数需要使用 @JvmStatic 来修饰。什么是伴生对象?click me

// kotlin
object Singleton {

    @JvmField
    var x = 10

    @JvmStatic
    fun printlnX() = println("x=$x")
}

class Area {

    companion object {

        @JvmField
        var defaultWith = 100

        @JvmStatic
        fun areaSize(height: Double) = defaultWith * height
    }
}

调用上述示例代码:

// java
public class MainJava {

    public static void main(String[] args) {
        System.out.println("---------- 顶层函数 ----------");
        System.out.println(ExKotlin.rectangleArea(100, 20));

        System.out.println("--------------------");
        Singleton.printlnX();

        System.out.println("--------------------");
        System.out.println(Area.areaSize(20));
    }
}
---------- 顶层函数 ----------
2000.0
--------------------
x=10
--------------------
2000.0

Process finished with exit code 0

四、可见性

  Java 和 Kotlin 都有4种可见性,但是除了 public 可以完全兼容外,其他的可见性都是有所区别的。

可见性 修饰符 类成员声明 顶层声明 说明
公有 public 所有地方可见 所有地方可见 public是默认修饰符
内部 internal 模块中可见 模块中可见 不同于java中的包
保护 protected 子类中可见 顶层声明中不能使用
私有 private 类中可见 文件中可见

  注意:kotlin 中没有 Java 的包私有可见性,而具有模块可见性 (internal)

// KotlinSeeable.kt

internal class Emplyee {
    internal var no: Int = 10   // 内部可见性Java端可见

    protected var job: String? = null   // 保护可见性Java端子类继承可见

    private var salary: Double = 0.0    // 私有可见性Java端不可见
        set(value) {
            if (value >= 0.0) field = value
        }

    lateinit var dept: Department   // 公有可见性Java端可见
}

open class Department {
    protected var no: Int = 0   // 保护可见性Java端子类继承可见

    var name: String = ""   // 公有可见性Java端可见
}

internal const val MAX_IN_COUNT = 300   // 内部可见性Java端可见

private const val MIN_IN_COUNT = 0  // 私有可见性Java端不可见

  注意:Kotlin 中 属性函数 默认可见性为public。当 函数 没有被 open 修饰(即:默认情况)是可不被继承状态。

public final class Emplyee {
   private int no = 10;
   @Nullable
   private String job;
   private double salary;
   @NotNull
   public Department dept;

   public final int getNo$kotlin_module() {
      return this.no;
   }

   public final void setNo$kotlin_module(int var1) {
      this.no = var1;
   }

   @Nullable
   protected final String getJob() {
      return this.job;
   }

   protected final void setJob(@Nullable String var1) {
      this.job = var1;
   }

   private final void setSalary(double value) {
      if (value >= 0.0D) {
         this.salary = value;
      }

   }

   @NotNull
   public final Department getDept() {
      Department var10000 = this.dept;
      if (var10000 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("dept");
      }

      return var10000;
   }

   public final void setDept(@NotNull Department var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.dept = var1;
   }
}

public class Department {
   private int no;
   @NotNull
   private String name = "";

   protected final int getNo() {
      return this.no;
   }

   protected final void setNo(int var1) {
      this.no = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }
}

public final class KotlinSeeableKt {
   public static final int MAX_IN_COUNT = 300;
   private static final int MIN_IN_COUNT = 0;
}

  注意:当 Kotlin 类函数被 Java 解释时,Kotlin 属性被映射为 Java 的成员变量,其修饰符均为 private,但其 gettersetter 方法的可见性会与 Kotlin 中属性可见性对应。lateint 修饰是个例,lateinit 修饰可见性保持一致。

public class MainJava {

    public static void main(String[] args) {
        Emplyee emp = new Emplyee();
        // 访问kotlin中内部可见性的Emplyee成员属性no
        int no = emp.getNo$kotlin_module();

        Department dept = new Department();
        // 访问kotlin中公有可见性的Department成员属性name
        dept.setName("市场部");

        // 访问Kotlin中公有可见性的Employee成员属性dept
        emp.setDept(dept);
        System.out.println(emp.getDept());

        // 访问kotlin中内部可见性的顶层属性MAX_IN_COUNT
        System.out.println(KotlinSeeableKt.MAX_IN_COUNT);
    }
}

五、生成重载函数

  Kotlin 的函数参数可以设置默认值,看起来像多个函数重载一样。但 Java 中并不支持参数默认值,只能支持全部参数函数。为了解决这个问题,可以在 Kotlin 函数前使用 @JvmOverloads 注解,Kotlin 编译器会生成多个重载函数。@JvmOverloads 注解的函数可以是 构造函数成员函数顶层函数,但 不能是抽象函数

class Animal @JvmOverloads constructor(val age: Int, val sex: Boolean = false)

class DisplayOverloading {
    @JvmOverloads
    fun display(c: Char, num: Int = 1) {
        println("$c $num")
    }
}

@JvmOverloads
fun makeCoffee(type: String = "卡布奇诺"): String {
    return "制作一杯${type}咖啡"
}
public final class KotlinOverloadsKt {
   @JvmOverloads
   @NotNull
   public static final String makeCoffee(@NotNull String type) {
      Intrinsics.checkParameterIsNotNull(type, "type");
      return "制作一杯" + type + "咖啡";
   }

   // $FF: synthetic method
   @JvmOverloads
   @NotNull
   public static String makeCoffee$default(String var0, int var1, Object var2) {
      if ((var1 & 1) != 0) {
         var0 = "卡布奇诺";
      }

      return makeCoffee(var0);
   }

   @JvmOverloads
   @NotNull
   public static final String makeCoffee() {
      return makeCoffee$default((String)null, 1, (Object)null);
   }
}

// DisplayOverloading.java
public final class DisplayOverloading {
   @JvmOverloads
   public final void display(char c, int num) {
      String var3 = "" + c + ' ' + num;
      boolean var4 = false;
      System.out.println(var3);
   }

   // $FF: synthetic method
   @JvmOverloads
   public static void display$default(DisplayOverloading var0, char var1, int var2, int var3, Object var4) {
      if ((var3 & 2) != 0) {
         var2 = 1;
      }

      var0.display(var1, var2);
   }

   @JvmOverloads
   public final void display(char c) {
      display$default(this, c, 0, 2, (Object)null);
   }
}

// Animal.java
public final class Animal {
   private final int age;
   private final boolean sex;

   public final int getAge() {
      return this.age;
   }

   public final boolean getSex() {
      return this.sex;
   }

   @JvmOverloads
   public Animal(int age, boolean sex) {
      this.age = age;
      this.sex = sex;
   }

   // $FF: synthetic method
   @JvmOverloads
   public Animal(int var1, boolean var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = false;
      }

      this(var1, var2);
   }

   @JvmOverloads
   public Animal(int age) {
      this(age, false, 2, (DefaultConstructorMarker)null);
   }
}

  通过 IDE 解释后,可以看出 Kotlin 中的参数默认值,在 Java 中实际是通过函数的重载来实现的。

public class MainJava {

    public static void main(String[] args) {
        Animal animal1 = new Animal(10, true);
        Animal animal2 = new Animal(10);

        DisplayOverloading dis1 = new DisplayOverloading();
        dis1.display('A');
        dis1.display('B', 20);

        KotlinOverloadsKt.makeCoffee();
        KotlinOverloadsKt.makeCoffee("摩卡咖啡");
    }
}

注意:别忘了上面的 @file:JvmName("自定义名") 的使用。

六、异常检查

  Kotlin 中没有受检查异常,在函数后面也不会有异常声明。如果有如下的 Kotlin 代码:

@file:JvmName("ErrorKt")

package cn.ak.kotmodule.kot

import java.text.SimpleDateFormat
import java.util.*

fun readDate(): Date? {
    val str = "201D-18-19"
    val df = SimpleDateFormat("yyyy-MM-dd") // 1️⃣抛出异常
    // 从字符串中解析日期
    return df.parse(str)
}

  上面代码第1️⃣行会抛出 ParseException 异常,这是因为解析的字符串不是一个合法的日期。在 Java 中 ParseException 是受检查异常,如果在 Java 中调用 readDate 函数,由于 readDate 函数没有声明抛出 ParseException 异常,编译器不会检查要求 Java 程序捕获异常处理。Java 调用代码如下:

public class MainJava {

    public static void main(String[] args) {
        ErrorKt.readDate();
    }
}

  这样处理异常不符合 Java 的习惯,为此可以在 Kotlin 的函数前加上 @Throw 注解,修改 Kotlin 代码如下:

@Throws(ParseException::class)
fun readDate(): Date? {
    val str = "201D-18-19"
    val df = SimpleDateFormat("yyyy-MM-dd") // 抛出异常
    // 从字符串中解析日期
    return df.parse(str)
}

  注意在 readDate 函数前添加注解 @Throws(ParseException::class),其中 ParseException 是需要处理的异常类。
  那么 Java 代码可以修改为如下捕获异常形式:

public class MainJava {

    public static void main(String[] args) {
        try {
            ErrorKt.readDate();
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

  当然在 Java 中除了 try-catch 捕获异常,还可以声明抛出异常。

上一篇 下一篇

猜你喜欢

热点阅读