Koltin - Koltin与Java互相调用

1、Kotlin调用Java

1.1、属性

Kotlin调用属性实际上就是访问getter、setter方法,因此Java类只要提供了getter方法,Kotlin就可将其当成只读属性。如果Java类提供了getter、setter方法,Kotlin就可将其当成读写属性。

注:其实Java是否包含了成员变量不重要,关键是getter、setter方法。

如果getter方法的返回值类型是boolean,该getter方法名也以is开头,此时Kotlin会将其当成属性名与getter方法同名的属性。比如boolean.isMarried()方法可作为Kotlin的只读属性,该属性名为isMarried.

1.2、Java中的类名、方法名、接口名等是Kotlin的关键字,则需要进行转义(``)

public class InMethod{

public void in(){

}}

fun main(args:Array<String>)

var im  = InMethod()

im.`in`()

}

1.3、对Java数组的处理

Kotlin的数组是不支持型变的,因此Array<String>不能赋值给Array<Object>。但Java数组是支持型变的,因此String[]可以直接赋值给Object[]变量。

此外,Java还支持int[]、long[]等基本类型的数组,这种数组可以避免拆装箱带来的性能开销。但Kotlin的Array类是不支持这种基本数据类型的数组,为此,Kotlin提供了ByteArray、ShortArray、......数组,这几种数组专门用于代替Java的int[]、long[]等基本类型的数组。

1.4、调用参数个数可变的方法

public class JavaVarargs{

public void test(int...nums){}

}

Kotlin==>

val jv =JavaVarargs()

jv.test(3,5,6,2)

val intArr = intArrayOf(1,2,3,5)

//将数组解开成多个元素

jv.test(*intArr)

1.5、Object的处理

Java的Object对应Kotlin的Any,又因为Any只声明了toString()、equals()、hashCode()方法,如何使用Object类中其他方法?

  • wait/notify/notifyAll

先转型再调用:(foo as java.lang.Object).wait()

  • getClass

obj::Class.java或obj.javaClass

  • clone

如果要让一个对象重写clone方法,则需要让该类实现kotlin.cloneable接口。

class Example:kotlin.cloneable{

override fun clone():Any{

}}

1.6、访问静态成员

Java类中的静态成员可以通过伴生对象的语法调用。

java.awt.BorderLayout.NORTH//访问java.awt.BorderLayout的NORTH静态成员(就像访问伴生对象的属性一样)

1.7、SAM转换(利用Lambda表达式来作为函数式接口的实例)

//使用Lambda表达式来创建函数式接口(Runnable)的对象

var  r  = Runnable{

。。。。。

}

1.8、Kotlin中调用JNI

Java使用native关键字,而Kotlin使用external关键字。

external fun foo(x:Int):Double

2、Java调用Kotlin

2.1、包级函数

所谓的包级函数是指Kotlin中直接定义的顶级函数。实际上Kotlin编译器会为整个Kotlin源文件生成一个类(只要该源文件中包含了顶级函数、顶级变量),这些顶级函数、顶级变量都会变成该类的静态方法、静态变量。

我们可以使用@JvmName注解来改变这个生成类名。

@file:Jvm("cutil")

package com.test

fun test(){

}

==>会生成一个test.class的类

如果想将多个文件中的顶级函数、顶级变量统一生成一个类,可以使用@JvmMultifileClass

2.2、实例变量

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

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

  • 该属性具有幕后字段

  • 该属性必须是非private访问控制的

  • 该属性不能使用open、override、const修饰

  • 不能是委托属性

class InstanceField(name:String){

@JvmField val myName = name

}

系统会将该属性的幕后字段暴露成实例变量(默认的访问权限是public)

public class InstanceFieldTest{

public static void main(string[] args){

InstanceField ins = new InstanceField("test");

System.out.println(ins.myName);//如果不用@JvmField修饰,则只能通过getter方法获取

}}

2.3、类变量

在对象声明和伴生对象中声明的属性会在该对象声明或包含伴生对象的类中具有静态幕后字段(类变量)。但这些变量通常是private访问权限。可以通过如下三种方式之一将它们暴露出来:

  • @JvmField

  • const

  • lateinit

class MyClass{

companion object{

@JvmField val name = "name的属性值"

}}

伴生对象本来就是用来弥补Kotlin没有static关键字的不足的,因此伴生对象中的属性实际上就相当于MyClass的类变量,但它默认是private访问权限的。通过使用@JvmField,该类变量就变成了与该属性具有相同访问控制符:public

const修饰的属性,不管是在顶层定义的属性还是在对象中定义的属性,只要使用了const修饰,它就变成了public static final修饰的类变量了。

在对象声明或伴生对象中的延迟初始化属性具有与该属性的setter方法相同的访问控制符的类变量。道理很简单:Java中并不支持对象声明,因此Kotlin的对象声明在Java中其实表现为一个单例类,对象声明得属性就是类变量。又由于lateinit属性需要等到使用时赋值,因此Kotlin必须将其暴露出来 - 否则Java程序无法对该类变量赋值。

2.4、类方法

可以肯定的是,Kotlin的顶级函数会被转换为类方法。

此外,Kotlin还可以将对象声明或伴生对象中定义的方法转换成类方法 - 如果这些方法使用@JvmStatic修饰的话。一旦使用这些注解,编译器就既会在相应对象的类中生成类方法,也会在对象自身中生成实例方法。

class Test {

companion object {

var name = "对象声明"

@JvmStatic

fun test() {

println(name)

}}}

==》

public final class Test {

@NotNull

private static String name = "对象声明";

public static final Test.CompanionCompanion = new Test.Companion((DefaultConstructorMarker)null);

@JvmStatic

public static final void test() {

Companion.test();

}

public static final class Companion {

@NotNull

public final String getName() {

return Test.name;

}

public final void setName(@NotNull String var1) {

Intrinsics.checkParameterIsNotNull(var1, "<set-?>");

Test.name = var1;

}

@JvmStatic

public final void test() {

String var1 = ((Test.Companion)this).getName();

System.out.println(var1);

}

private Companion() {

}

// $FF: synthetic method

public Companion(DefaultConstructorMarker $constructor_marker) {

this();

}}

==》 Test.Companion.test() 或 Test.test()都可以

2.5、获取KClass

kotlin.jvm.JvmClassMappingKt.getKotlinClass(clazz);

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

在Kotlin中定义两个同名函数,但JVM平台无法区分这两个函数,典型的情况就是类型擦除引起的问题。

fun List<String>.filterValid():List<String>{

}

fun List<Int>.filterValid():List<Int>{

}

因此,可以通过@JvmName解决冲突

fun List<String>.filterValid():List<String>{

}

@JvmName("filterValidInt")

fun List<Int>.filterValid():List<Int>{

}

2.7、生成重载

Kotlin为方法提供了参数默认值来避免函数重载过多的问题。但对这种参数有默认值的方法,编译器默认只会生成一个方法:默认带所有参数的方法。

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

@JvmOverload

fun test(name:String,len:Int = 26,price:Double = 2.2){

}

加了注解,编译器会为我们生成如下三个方法:

public static final void test(String,Int,Double)

public static final void test(String,Int)

public static final void test(String)

需要指出的是,如果一个类的所有构造器的参数都有默认值,这样就能以构造参数的默认值来调用构造器(就像调用无参的构造器),因此JVM平台相当于生成了一个public的无参构造器,这种情况是不需要使用@JvmOverload注解的。

2.8、泛型

Kotlin支持声明处型变,而Java仅支持使用处型变。因此,必须转成Java的使用处型变。规则:

  • 对于协变类型的泛型类Bar<out T>,当它作为参数出现时,Kotlin会自动将Bar<Base>类型的参数替换成Bar< ? extends Base>

  • 对于逆变类型的泛型类Bar<in T>,当它作为参数出现时,Kotlin会自动将Bar<Base>类型的参数替换成Bar< ? superBase>

  • 不管对于协变类型的泛型类Bar<out T>,还是逆变类型的泛型类Bar<in T>,当它作为返回值出现时,Kotlin不会生成通配符

除了上述的这种自动的机制之外,Kotlin还提供了注解,用于控制是否生成通配符。

  • @JvmWildcard:指定在编译器默认不生成通配符的地方强制生成通配符;

  • @JvmSuppressWildcrads:指定在编译器默认生成通配符的地方强制不生成通配符;

3、Kotlin反射

Kotlin把函数和属性当成"一等公民",并可通过反射直接获取函数、属性的引用。

注:使用Kotlin的反射API需要单独的添加JAR包(kotln-reflect.jar),这样可以方便程序在不使用反射时减小运行库的大小。

3.1、Koltin的类引用

Koltin的类引用是KClass对象,Java的类引用是java.lang.Class对象,它们两者是不同的,如果需要通过KClass获取对应的java.lang.Class对象,则可调用KClass对象的Java属性。

val c = MyClass::class

如果已有一个kotlin对象,则同样可通过::class语法来获取该对象的类引用。例如:val c = myObject::class

通过KClass对象可以得到大量的KFunction、KProperty(它们都是KCallable的子类)等对象,这些对象分别代表了该类所包含的所有的方法(包含构造器)、属性等,程序可以通过这些对象来执行实际的功能。比如创建实例、调用方法等。

a)创建对象

获取KClass对象之后,调用该对象的createInstance()方法即可创建该类的实例,该方法总是调用KClass所代表类的无参数的构造器来创建实例。

如果需要调用有参数的构造器来创建实例,则可通过KClass的constructors属性来获取所有的构造器,该属性返回Collection<Function>集合对象,这意味着构造器的本质依然是一个函数。

示例:

var clazz = Item::class

var inst = clazz.createInstance()

var cons = clazz.constructors

cons,foreach{

if(it.parameters.size == 2){

val inst2 = it.call("kotlin",78.8)//调用带两个参数的构造器创建实例

}}

b)构造器引用

构造器的本质也是一个函数,即一个返回值为当前类的实例的函数。因此,程序可将构造器引用当成函数使用。

获取构造器引用=》::类名,比如::Item,即可引用该类的主构造器

在某些时候,如果获取Kotlin构造器引用对应的java构造器对象,则可以通过调用KFunction的扩展属性javaConstructor来实现。 例如::Item.javaConstructor

需要说明的是,如果要调用构造器引用的javaConstructor属性,则需要导入kotlin.reflect.jvm包 。

c)调用方法

方法.call(方法的调用者,参数1,参数2,......)

比如:

f.call(inst,“kotlin2”,99)

d)函数引用

Koltin程序可以获取函数的引用,把函数当成参数传入另一个函数中。

用法:

::函数名

此外,需要说明的是,如果需要引用类的成员方法或扩展方法,那么就需要进行限定。例如String::toCharArray才能表明引用的是String的toCharArray方法,单纯的使用::toCharArray不行。

String()::toCharArray函数引用的类型也不是简单的()->CharArray类型,而是String.()->CharArray类型。

在某些时候,如果要获取Kotlin函数引用对应的Java方法对象,则可通过调用KFunction的扩展属性javaMethod来实现。

::abs.javaMethod

需要说明的是,如果要调用函数引用的javaMethod属性,则需要导入kotlin.reflect.jvm包 。

e)访问属性值

获取KClass对象之后,也可通过KClass对象来获取该类包含的属性。Kotlin为属性提供了众多的API:

KProperty:代表通用的属性。它是KCallable的子接口。

KMutableProperty:代表通用的读写属性。它是KProperty的子接口。

KProperty0:代表无需调用者的属性(静态属性)。它是KProperty的子接口。

KMutableProperty0:代表无需调用者的读写属性(静态读写属性)。它是KProperty0的子接口。

KProperty1:代表需要一个调用者的属性(成员属性)。它是KProperty的子接口。

KMutableProperty1:代表需要一个调用者的读写属性(成员读写属性)。它是KProperty1的子接口。

KProperty2:代表需要2个调用者的属性(扩展属性)。它是KProperty的子接口。

KMutableProperty2:代表需要2个调用者的读写属性(扩展读写属性)。它是KProperty2的子接口。

f)属性引用

Kotlin同样提供了::属性名的形式获取属性引用,获取属性引用也属于前面介绍的Kproperty及其子接口的实例。

var foo = "测试属性"

val topProp = ::foo

topProp.set("hahah")

由于Kotlin属性会对应Java的3种成员,因此KProperty包含如下三个扩展属性:

javaField(幕后字段)、javaGetter(getter方法)、javaSetter(setter方法)

猜你喜欢

转载自blog.csdn.net/json_it/article/details/81841107