在java中调用kotlin代码也是很容易的事
一个kotlin属性希望被编译通过,那么它需要有以下java元素:
- getter方法,方法名要以get作为前缀
- setter方法,方法名要以set作为前缀
- 私有(private)字段,跟属性同名(只有属性才有隐性字段)
例如,var firstName: String 要通过编译需要有如下java声明:
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
如果属性名字开头是’is’,这里有不同的映射规则:getter方法还是和属性名一样,但set方法就需要用set来代替is作为方法名字的开头,例如,有一个属性是isOpen,它的getter方法可以是isOpen,setter方法就是setOpen。这个规则可以应用在所有类型中,除了Boolean类型之外。
所有的函数和属性都声明在org.foo.bar包下的example.kt文件中,包含拓展函数,编译之后就变成java类org.foo.bar.ExampleKt中的静态方法.
// example.kt
package demo
class Foo
fun bar() {
}
// Java
new demo.Foo();
demo.ExampleKt.bar();
生成Java类型的名字可以通过@JvmName注解来更改.
@file:JvmName("DemoUtils")
package demo
class Foo
fun bar() {
}
// Java
new demo.Foo();
demo.DemoUtils.bar();
如果有多个文件生成相同的java类名(相同包名和相同类名,或者相同@JvmName注解名)通常是一个错误,然而,编译器也有能力生成一个java类文件包含这些所有文件的声明和类名,要开启这个虚拟功能,需要在所有文件中使用@JvmMultifileClass注解:
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun foo() {
}
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun bar() {
}
// Java
demo.Utils.foo();
demo.Utils.bar();
如果你需要在Kotlin张暴露字段给java使用,你需要使用@JvmField注解来标记这个属性,这个字段和原先声明的属性有同样的可见性,如果它有隐性字段也可以使用@JvmField来标记,但这个字段不可以是私有(private),也不需要有open,override,const修饰,并且它不可以是委托属性.
class C(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(C c) {
return c.ID;
}
}
延迟初始化属性(late-Initialized properties)也可以暴露为字段,这个字段的可见性和lateinit属性的可见性是一样的。
kotlin的属性可以声明为命名对象或同级对象,这个对象可以在允许有静态隐性字段的命名对象中或包含同级对象的类中.
通常这些字段都是私有的(private),但它们却可以以下面任何一种方式来暴露:
- @JvmField 注解
- lateinit 修饰符
- const 修饰符
使用@JvmField注解标记的字段的可见性和属性本身的可见性是一样的:
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator = compareBy { it.value }
}
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class
一个对象中的延迟初始化属性或有隐性字段的同级对象的可见性和属性本身一样:
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class
被const标记的属性在java中会自动转为静态属性
// file example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
Java中:
int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;
像前面提到的一样,kotlin中包级下的函数就是静态方法,如果使用@JvmStatic注解标记命名对象的函数或同级对象的函数,那么kotlin就会生成对象的静态方法,如果使用这个注解,这个对象类的闭合范围内的方法和这个对象的实例本身,在编译器编译的时候都会生成对应的静态方法和静态实例,例如:
class C {
companion object {
@JvmStatic fun foo() {}
fun bar() {}
}
}
现在,在java中foo是静态的,但bar却不是,
C.foo(); // works fine
C.bar(); // error: not a static method
C.Companion.foo(); // instance method remains
C.Companion.bar(); // the only way it works
对象也是同样的:
object Obj {
@JvmStatic fun foo() {}
fun bar() {}
}
在java中:
Obj.foo(); // works fine
Obj.bar(); // error
Obj.INSTANCE.bar(); // works, a call through the singleton instance
Obj.INSTANCE.foo(); // works too
@JvmStatic注解也可以应用在对象的属性或者同级对象中getter和setter是静态的对象或者包含同级对象的类。
Kotlin的可见性映射到java中的规则如下:
- private 成员 编译后也是 private成员
- private 顶级声明编译后变成包级声明
- protected 依然是protected(==注意,java中允许同级包下其他类访问,但kotlin中不允许,因此java中的类有更开阔的访问==)
- internal 声明变成public,内部类的成员通过名字来重整,确保它能在java中更方便的使用并且允许重载相同签名的成员,但根据kotlin规则来说,它们彼此之间不可见
- public 依然是public
有时候你需要用KClass类型为参数来调用kotlin的一些方法,这里并没有自动的把KClass转为Class,所以你应该手动处理执行,以达到跟Class.kotlin 拓展属性相同的功能.
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
有时候我们在Kotlin中的函数名,在jvm中却想有不同的字节码名,很多特殊这样的例子产生都是因为类型搽除:
fun List<String>.filterValid(): List<String>
fun List.filterValid(): List
这两个函数并不可以定义为一模一样,因为它们的jvm签名是一样的:filterValid(Ljava/util/List;)Ljava/util/List;.在kotlin中,如果我们真的需要有相同的名字,我们可以使用@JvmName注解来标识,并配以不同的名字来做参数:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List.filterValid(): List
在kotlin中可以通过相同的名字filterValid来访问,但在java中却通过filterValid和filterValidInt来访问.
相同的做法应用还可以应用在当我们有一个属性x,同时还有一个函数getX():
val x: Int
@JvmName("getX_prop") get() = 15 fun getX() = 10
通常来说,如果你使用默认的参数值来写一个kotlin方法,在java中就只有全部签名并且全部参数也会出现,如果你需要展开更多重载给java调用的时候,你需要使用@JvmOverloads注解。
@JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
...
}
因为每个参数都有默认值,因此它会生成一个额外的重载,这个重载它有参数并且参数列表移除时所有参数都在它的右边,在这个例子中,就会下面这个方法:
// Java
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }
这个注解同样也适用在构造方法,静态方法等,但是它不可以应用在抽象方法中。
==注意,在第二构造方法中描述的一样,如果一个类的所有构造方法的参数都有默认值时,一个无参公开的构造方法就会生成,这个方式中即使没有@JvmOverloads注解也可以正常运行==
像我们前面提到的一样,kotlin中并不需要异常检测,那么一般来说,kotlin函数的java方式也不需要抛出异常,因此,如果我们在kotlin中有这么一个函数:
// example.kt
package demo fun foo() {
throw IOException()
}
然后我们在java中调用它并捕获异常:
// Java
try {
demo.Example.foo();
}
catch (IOException e) { // error: foo() 并没有在异常列表中声明IOException
// ...
}
编译的时候java编译器会给出一个错误提示,因为foo()并没有声明IOException异常,要解决这个问题,我们可以在kotlin中使用@Throws注解:
@Throws(IOException::class)
fun foo() {
throw IOException()
}
当在java中调用kotlin函数,没有人可以阻止我们传递null给一个非空参数,这就是kotlin为什么要创建运行时检测所有公开函数是否期望非空的原因,在这个情况下,我们在java中很快就可以得到一个NullPointerException异常.
当kotlin的类像声明位置变量中那样来做的时候,它们在java中的用法就有两种选择,现在让我们来看看下面的这个类和它的两个函数是怎么使用:
class Box< out T>(val value: T) interface Base class Derived : Base fun boxDerived(value: Derived): Box<Derived> = Box(value) fun unboxBase(box: Box<Base>): Base = box.value
我们非常希望它们在java中可以是这样:
Box boxDerived(Derived value) { ... }
Base unboxBase(Box box) { ... }
在kotlin中我们可以这样使用unboxBase(boxDerived(“s”)),但在java中这是不可能的,因为java中的Box类在它的参数T中是不可变的,另外Box不是Box的子类型,要达到这个方式我们需要下面这样定义unboxBase:
Base unboxBase(Box< ? extends Base > box) { ... }
在这里,我们使用了java的通配符(? extends Base)来模拟声明位置变量,因为它代码中的全部.
为了要使Kotlin的API都能工作在java中,当作为参数出现时,我们生成了Box 作为Box< ? extends Super>的协变定义,当它有返回值时,我们不能用通配符,因为java客户端必须处理它们,所以,我们例子中的函数事实上被翻译成以下这样:
// return type - 不是通配符
Box boxDerived(Derived value) { ... }
// parameter - 通配符
Base unboxBase(Box< ? extends Base> box) { ... }
==注意,当参数类型是final的时候,通常不可以使用通配符,所以Box一直是Box,它不会再改变==
如果我们需要使用通配符,但默认又没有生成时,我们可以使用@JvmWildcard注解:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box< ? extends Derived> boxDerived(Derived value) { ... }
在另外一个方面,如果我们不需要通配符但它有生成,我们可以用@JvmSuppressWildcards注解:
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// is translated to
// Base unboxBase(Box box) { ... }
==注意,@JvmSuppressWildcards不只可以用在个别类型通配符上,在整个声明中,比如函数或类,在它们内部都可以引起抑制通配符==
Nothing类型是非常特殊的,因为它在java中并没有实际的对应类型,的确,每个java引用类型,包含java.lang.Void,都可以接受null为值,但 Nothing都不允许这些。因此这个类型实际上并没有出现在java的世界里,这就是为什么在kotlin中当参数类型为Nothing时就生成原始类型来代替的原因:
fun emptyList(): List = listOf()
// is translated to
// List emptyList() { ... }