大多数情况下,你不需要关注这个问题。但是,如果你的代码中包含了部分Java代码,理解这些注解将帮助你解决很多棘手问题。
产生这个问题的根本原因在于:Kotlin语言与Java语言的设计思路不同,部分特性属于Java语言独有,例如静态变量。部分特性属于Kotlin语言独有,例如逆变和协变。
为了抹平这些差异,Kotlin语言提供了一个绝佳的思路,通过添加注解可以改变Kotlin编译器生成的Java字节码,使之按照Java语言可以理解的方向进行,从而实现兼容。
问题答疑:Kotlin语言与Java字节码有什么关系?为什么Kotlin编译器会生成Java字节码?
不管是Kotlin语言还是Java语言都是建立在JVM平台上面的编程语言,其最终都需要编译成JVM可以识别的Java字节码才能被正确执行。这也是为什么Kotlin语言与Java可以完全互通的原因之一,不要将Java与Java平台混为一谈。
接下来我们先来看第一个注解,也是最常用到的一个注解:
Kotlin编译器默认会将类中声明的成员变量编译成私有变量,Java语言要访问该变量必须通过其生成的getter方法。而使用上面的注解可以向Java暴露该变量,即使其访问变为公开(修饰符变为public)。
我们来做一个实验:
1)新建Person.kt
,添加如下代码:
class Person {
@JvmField
var name: String? = null
}
2)新建Client.java
,添加如下代码,尝试访问Person
类中的变量name
:
public final class Person {
private String name;
public final String getName() {
return this.name;
}
public final void setName(@Nullable String var1) {
this.name = var1;
}
}
在添加@JvmField
属性前我们试图通过p.name
的方式进行访问,编译器出现报错。因为,默认生成的成员变量name
是私有的。而添加该注解之后我们居然可以正常访问了。
由此可见,@JvmField
注解的确使生成的字节码发生了变化,我们将字节码用Java代码来表示,具体发生的变化类似下面代码发生的变化:
添加注解之前
public class Client {
public static void main(String[] args) {
Person p = new Person();
// 在添加@JvmField注解之前,这样访问会报错
// 只能通过p.getName()的方式进行访问
String name = p.name;
}
}
添加注解之后
public final class Person {
public String name;
}
以上场景是将@JvmField
注解添加到普通变量上方,如果添加到伴随对象的成员变量上方,会发生什么呢?我们来试试看:
class Person {
var name: String? = null
companion object {
@JvmField
val GENDER_MALE = 1
}
}
public static void main(String[] args) {
// 未添加之前
// int gender = Person.Companion.getGENDER_MALE();
// 添加之后,可直接访问
int gender = Person.GENDER_MALE;
System.out.println(gender);
}
同样地,添加注解之后我们可以通过点语法直接对其进行访问。
由此可见,@JvmField
注解会使伴随对象在伴生类中生成静态成员变量,通过伴生类可直接对其进行访问。
@JvmField
注解可改变字节码的生成,其作用的目标是类成员变量或伴随对象成员变量。作用在类成员中可使该变量对外暴露,通过点语法直接访问。即将私有成员变量公有化(public),并去掉setter/getter方法。作用在伴随对象成员变量中,可以使