Java调用Kotlin


一、属性

Kotlin属性可编译成如下三个成员:

  • 一个private实例变量,实例变量名与属性名相同。
  • 一个getter方法,方法名为属性名添加get前缀。
  • 一个setter方法,方法名为属性名添加set前缀。

如果属性名以is开头,那么该属性对应的生成规则略有差别:

  • 实例变量名、getter方法名与属性名相同。
  • setter方法名为属性名去掉is前缀,添加set前缀。

二、包级函数

在Kotlin中直接定义的顶级函数。

Kotlin编译器会为整个Kotlin源文件生成一个类,顶级函数、顶级变量都会变成这个类的静态方法、静态变量。

var name: String = ""
fun test() {

}

默认情况下,Kotlin为包含顶级成员的源文件所生成类的类名总是文件名+Kt后缀;但Kotlin也允许使用@JVMName注解来改变Kotlin编译生成的类名。

//指定生成的类
@file:JvmName("FkUtils")

package test0711

var name: String = ""
fun test() {
    println("FkUtils的test()方法")
}

多个Kotlin源文件要生成相同的Java类,可以使用@JvmMultifileClass注解。

//指定生成的类
@file:JvmName("CiUtils")
//指定为多个文件中的顶级成员统一生成一个类
@file:JvmMultifileClass
package test0711

fun test1(){
    println("CiUtils的test1()方法")
}
//指定生成的类
@file:JvmName("CiUtils")
//指定为多个文件中的顶级成员统一生成一个类
@file:JvmMultifileClass
package test0711

fun bar(){
    println("CiUtils的bar()方法")
}

在Java中调用:

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        CiUtils.test1();
        CiUtils.bar();
    }
}

三、实例变量

Kotlin允许将属性暴露成实例变量。只要使用@JvmField修饰该属性即可,暴露的属性将会和Kotlin属性具有相同的访问权限。

使用@JvmField将属性暴露成实例变量的要求如下:

  • 该属性具有幕后字段。
  • 该属性必须是非private访问控制的。
  • 该属性不能使用open、override、const修饰。
  • 该属性不能是委托属性。
class InstanceField(name: String) {
    @JvmField
    val myName = name
}

在Java中访问该类的对象的实例变量:。

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        InstanceField ins = new InstanceField("Kotlin");
        //访问实例变量
        System.out.println(ins.myName);
    }
}

四、类变量

在命名对象(对象声明)或伴生对象中声明的属性会在该命名对象或包含伴生对象的类中具有静态幕后字段。

可以通过如下三种方式将private访问修饰的类变量暴露出来:

  • 使用@JvmField注解修饰
  • 使用lateinit修饰
  • 使用const修饰
class MyClass {

    //使用companion修饰的伴生对象
    companion object MuObject1 {
        @JvmField
        val name = "name属性值"
    }

}

在Java中访问MyClass的name属性:

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        //访问MyClass的name类变量
        System.out.println(MyClass.name);
    }
}

在命名对象或伴生对象中的延迟初始化属性具有与该属性的setter方法相同的访问控制符的类变量。

    //定义命名对象
    object MyObject {
        //定义延迟初始化属性
        lateinit var name: String
    }

在Java中调用:

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        //直接访问MyObject类的name类变量
        MyClass.MyObject.name = "Kotlin";
        System.out.println(MyClass.MyObject.name);
    }
}

在Kotlin中使用const修饰的属性,不管是在顶层定义的属性,还是在对象中定义的属性,都会变成有public static final修饰的类变量。

const val MAX = 257

object Obj {
    const val NAME = "Kotlin"
}

class Css {
    companion object {
        //使用const修饰的变量
        const val SITE = "www.coderlearning.cn"
    }
}

在Java中调用:

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        System.out.println(ConstKt.MAX);
        System.out.println(Obj.NAME);
        System.out.println(Css.SITE);
    }
}

五、类方法

Kotlin的顶级函数会被转换成类方法。

Kotlin可以将命名对象或伴生对象中定义的方法转换成类方法,这些方法需要使用@JvmStatic修饰。

一旦使用该注解,编译器就既会在相应对象的类中生成类方法,也会在对象自身中生成实例方法。

package test0711

//指定零个父类型的命名对象
object MyObject {
    //方法
    fun test() {
        println("test方法")
    }

    @JvmStatic
    fun foo() {
        println("有@JvmStatic修饰的foo()方法")
    }
}

class MyTestClass {
    //省略名字的伴生对象
    companion object {
        //方法
        fun test() {
            println("test方法")
        }

        @JvmStatic
        fun output(msg: String) {
            for (i in 1..6) {
                println("<h${i}>${msg}</h${i}>")
            }
        }
    }
}

在Java中调用:

package test0711;

public class StaticMethodTest {
    public static void main(String[] args) {
        //通过INSTANCE访问MyObject的单例,通过单例访问test()方法
        MyObject.INSTANCE.test();
        //通过INSTANCE访问MyObject的单例,通过单例访问foo()方法
        MyObject.INSTANCE.foo();
        //foo()方法有@JvmStatic修饰,所以也是MyObject的类方法
        MyObject.foo();

        //通过Companion访问MyTestClass的伴生对象,通过伴生对象访问test()方法
        MyTestClass.Companion.test();
        //通过Companion访问MyTestClass的伴生对象,通过伴生对象访问output()方法
        MyTestClass.Companion.output("Kotlin");
        //output()方法有@JvmStatic修饰,所以也是MyTestClass的类方法
        MyTestClass.output("Kotlin");
    }
}

六、访问控制符的对应关系

Kotlin的访问控制符与Java的对应关系如下:

  • private成员:依然编译成Java的private成员。
  • protected成员:依然编译成Java的protected成员。
  • internal成员:会编译成Java的public成员。但编译成Java类时会通过名字修饰来避免在Java中被意外使用到。
  • public成员:依然编译成Java的public成员。

七、获取KClass

在Java中获取Kotlin的KClass对象,必须通过调用Class.kotlin扩展属性的等价形式来实现。

下面代码用于获取指定Class对象的KClass对象。

public class KClassTest {

    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("java.util.ArrayList");
        System.out.println(clazz);
        //获取Class对象的KClass对象
        KClass kc = JvmClassMappingKt.getKotlinClass(clazz);
        System.out.println(kc);
    }
}

八、使用@JvmName解决签名冲突

有时候,在Kotlin中要定义两个同名函数,但是JVM平台无法区分这两个函数。

package test0711

fun List<String>.filterValid(): List<String> {
    val result = mutableListOf<String>()
    for (s in this) {
        result.add(s)
    }
    return result.toList()
}

@JvmName("fileterValidInt")
fun List<Int>.filterValid(): List<Int> {
    val result = mutableListOf<Int>()
    for (i in this) {
        if (i < 20) {
            result.add(i)
        }
    }
    return result.toList()
}

使用了@JvmName(“filterValidInt”)来标注一个方法,这个方法在JVM平台上会编译成filterValidInt()方法。

@JvmName注解也适用于属性x和函数getX()共存。

val x: Int
    @JvmName("getX_prop")
    get() = 15

fun getX() = 10

九、生成重载

为了能让编译器为带参数默认值的方法生成多个重载的方法,可以使用@JvmOverloads注解。

@JvmOverloads适用于构造器、静态方法等。不适用于抽象方法。

@JvmOverloads
fun test(name:String,len:Int=20,price:Double=2.2){
    println(name)
    println(len)
    println(price)
}

在Java中调用:

public class JvmMultifileClassTest {
    public static void main(String[] args) {
        NameConflictKt.test("Kotlin",100,10.0);
        NameConflictKt.test("Kotlin",100);
        NameConflictKt.test("Kotlin");
    }
}

如果一个类的所有构造器的参数都有默认值,就能以构造参数的默认值来调用构造器(就像调用无参数的构造器)。

十、checked异常

如果希望Java调用函数时编译器会检查IOException,可以使用@Throws注解修饰函数。

@Throws(IOException::class)
fun foo() {
    val fis = FileInputStream("a.txt")
}

十一、泛型的型变

对于Kotlin的声明处型变,必须转换成Java的使用处型变,规则如下:

  • 对于协变类型的泛型类Bar,当它作为参数出现时,Kotlin会自动将Bar类型的参数替换成Bar<? extends Base>。
  • 对于逆变类型的泛型类Foo,当它作为参数出现时,Kotlin会自动将Foo类型的参数替换成Foo<? super Sub>。
  • 不管是协变类型的泛型类Bar,还是逆变类型的泛型类Foo,当它作为返回值出现时,Kotlin不会生成通配符。
open class Base
class Sub : Base()
class Box<out T>(val value: T)

fun boxSub(value: Sub): Box<Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value

除了Kotlin默认的转换规则之外,Kotlin还可以使用注解控制是否生成通配符。

  • @JvmWildcard:该注解可指定在编译器默认不生成通配符的地方强制生成通配符。
  • @JvmSuppressWildcards:该注解可指定在编译器默认生成通配符的地方强制不生成通配符。

下面示范了@JvmWildcard注解的用法

package test0711

open class Base
class Sub : Base()
class Box<out T>(val value: T)

fun boxSub(value: Sub): Box<@JvmWildcard Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value

下面示范了@JvmSuppressWildcard注解的用法

package test0711

open class Base
class Sub : Base()
class Box<out T>(val value: T)

fun boxSub(value: Sub): Box< Sub> = Box(value)
fun unboxBase(box: Box<@JvmSuppressWildcard Base>): Base = box.value

@JvmSuppressWildcard注解不仅可修饰单个泛型形参,还可修饰整个声明,从而阻止编译器为整个类或函数中的声明处型变生成通配符。

学海无涯苦作舟

我的微信公众号.jpg


文章作者: HunterArley
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 HunterArley !
评论
  目录