访问 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
声明的属性会生成 getter
和 setter
两个函数,但 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
注解、lateinit
或 const
来修饰。什么是伴生对象?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
,但其 getter
和 setter
方法的可见性会与 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
捕获异常,还可以声明抛出异常。