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

  • 访问 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️⃣
    }
}

  • 使用 @file:JvmName("自定义名") 访问

  如果你觉得上面使用 文件名 来调用函数和属性不够友好,但还不想修改 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 可以完全兼容外,其他的可见性都是有所区别的。

  • Java 可见性,默认为包私有


    默认为包私有
  • Kotlin 可见性

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

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

  • 将 Java 可见性 和 Kotlin 可见性对比,可知 Kotlin 中没有默认包私有可见性,而 Java 中没有内部可见性。
    (1) Kotlin 私有可见性
      由于 Kotlin 私有可见性可以声明类中成员,也可以声明顶层成员。那么映射到 Java 分为两种情况:
      a、Kotlin 类中私有成员映射到 Java 类中私有实例成员。
      b、Kotlin 中私有顶层成员映射到 Java 中私有静态成员。
    (2) Kotlin 内部可见性
      由于 Java 中没有内部可见性,那么 Kotlin 内部可见性映射为 Java 公有可见性。
    (3) Kotlin 保护可见性
      Kotlin 保护可见性映射为 Java 保护可见性。
    (4) 公有可见性
      Kotlin 公有可见性映射为 Java 公有可见性。

  • 示例代码

// 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 修饰(即:默认情况)是可不被继承状态。

  • 通过 IDE 解释后的 Java 代码如下:
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 修饰可见性保持一致。

  • Java 调用 Kotlin 示例
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 注解的函数可以是 构造函数成员函数顶层函数,但 不能是抽象函数

  • kotlin 示例代码:
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}咖啡"
}
  • IDE 解释 Java 代码:
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 中实际是通过函数的重载来实现的。

  • Java 调用 Kotlin 含有默认参数的函数:
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 捕获异常,还可以声明抛出异常。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • Kotlin的类和接口与Java的类和接口是有一定的区别的。Kotlin的接口是可以包含属性声明。Kotlin默认...
    程自舟阅读 10,307评论 0 11
  • 写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kot...
    胡奚冰阅读 1,409评论 5 11
  • 面向对象编程(OOP) 在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统、集合类以及泛型相关的知识。...
    Tenderness4阅读 4,414评论 1 6
  • Kotlin 调用 Java 由于 Kotlin本身并没有提供强大的类库支持,Kotlin只是一种语言,因此 Ko...
    凌寒天下独自舞阅读 13,988评论 0 10
  • 近期抽时间看了几篇短篇,这一年快过去了,或许真的写不出和市场完全接轨的文章,在一切市场化的时代下,在阅读多元化的多...
    绿竹君阅读 195评论 0 1