java与Kotlin互调
第一部分 Kotlin中调用Java
1、属性
Kotlin调用、修改属性、就是访问getter、setter方法,只要Java提供getter、setter方法,该属性在Kotlin中就会被当成读写变量,只提供了getter方法,被当成只读属性;
注意:如果getter返回值是boolean类型,该getter方法名是以is开头,Kotlin会将其当成属性名与getter方法名同名的属性;如:public boolean isMarried()作为kotlin中的只读属性,属性名为ismarried;
java中是否提供成员变量无所谓,只要提供对应的getter、setter变量,在kotlin中就会生成对应的属性;
2、kotlin与java中关键字不一致
Kotlin中关键字比Java多,比如:in、object等在java中都不是关键字,但在kotlin中确实关键字;就会出现一种情况:
这些关键字在java中作为类名、方法名、接口名等 ,kotlin中在使用时必须使用"`"对关键字转义;
如:java:
public void in(){}
kotlin:
对象.`in`()
3、Kotlin中数组不支持型变,java中数组支持型变
在java中数组是型变的,即String[]可以赋值给Object[]:
String[] str=new String[10]
Object[] obj=str
//下面就会发生ArrayStoreException异常
obj[0]=1
在koltlin中,str是不能赋值给obj的,很好的避免运行时出现异常;
由于kotlin中取消了基本数据类型,Kotlin中提供了ByteArray、ShortArray等数组用于替代byte[]、short[]等java基本类型数组
4、SAM转换
Java8中支持Lambda表达式来作为函数式接口的实例,Kotlin同样支持该功能:
val pre=Predicate<Int>{it >5}
5、Kotlin的已映射类型
java基本类型映射Kotlin的Int、Short等引用类型,Kotlin中无基本数据类型;
Java中的包装类映射为对应的类的引用类型如:Integer----->Int?
java中常用类映射:
Object---->Any!
java.lang.Cloneable----->kotlin.Cloneable
java.lang.Comparable----->kotlin.Comparable!
java.lang.Enum--------->kotlin.Enum!
java.lang.Annotation----->koltin.Annotation!
java.lang.Deprected---->kotlin.Deprected!
java.lang.CharSequence------>kotlin.CharSequence!
java.lang.String--------->kolin.String!
java.lang.Number------>kotlin.Number!
java.lang.Throwable------>kotlin.Throwable!
6、泛型转换
Kotlin不支持java中的通配符语法,java中的通配符将转化成使用处型变;java中的原始类型将转化为星号投影;
Foo<? super Bar>------>Foo<out Bar!>!
Foo<? extends Bar>----->Foo<in Bar!>!
Foo<*>--------->Foo<out Any?>!
Foo------->Foo<*>
7、调用可惨参数方法
Java中支持为可变形参直接传入一个数组实参,Kotlin中不支持;Kotlin要求只能传入多个值或者使用"*"解开数组的方式来传入多个数组元素作为参数值;
class Util{
public void test(int... nums){}
}
kotlin中调用:
val intArr=intArryOf(1,4,6)
Util().test(*intArr)
8、Object的处理
java.lang.Object对应Kotlin中的Any类,但是Any类值声明了toString()、hashCode()、eqluas()方法,没有如:wait()、notify()、notifyAll()等方法;
在Kotlin中如果要调用这些方法:可以将Any类转型为java.lang.Object
(foo as java.lang.Object).wait()
- 获取对象的java类
java中是通过Object的getClass()方法
kotlin中是通过以下两种方式:
- obj::class.java
- obj.javaClass
-
如果要让对象重写clone()方法,需要让该类实现kotlin.Cloneable接口
-
java.lang.Object的finalize()方法主要用于完成资源工作清理,GC回收对象之前,JVM会自动调用该对象的finalize()方法。如果重写finalize()方法,则只需要在类中实现该方法既可,不需要使用override关键字(因为Any类中没有声明finalize()方法)
class Foo{
protected fun finalize(){}
}
9、访问静态成员
kotlin未提供static关键字,Kotlin中处理嵌套类算是静态成员外,都是实例成员。因此,Kotlin提供了伴生对象实现静态成员。Java中的静态成员可以通过伴生对象的语法来调用:
第二部分 Java中调用Kotlin
1、属性
kotlin中属性相当于Java中成员变量、getter、setter方法;
var name:String
编译生成的Java成员:
private String name
public void setName(String name){
}
public String getName(){}
如果属性是以“is”开头,编译生成的Java对应getter方法稍有不同:
getter方法名不在是get+属性名,而直接就是属性名
var isMarried:Int
生成的Java:
private int isMarried
public void setMarried(int married){
}
public int isMarried(){
}
2、包级函数
所谓包级函数即直接定义在kt文件中,而不是定义在类中的;kt文件会被编译器生成kt文件名+Kt的Java类,而这些顶级函数、属性都会成为该Java类的静态成员变量、静态成员方法
如果你不想生成默认名字(kt文件名+Kt.java)的java类,你可以通过@JvmName注解来指定编译的对应Java类名;
还有一种极端情况:多个kotlin源文件生成相同的java类(包名相同其类名相同,或者指定了相同的@JvmName注解),默认这种情况下会报错,但是Kotlin支持使用@JvmMultifileClass注解将多个源文件统一生成一个Java类,该java类中将包含这些源文件中的所有顶级函数(静态函数)、变量(静态变量);
@file:JvmName("CUtils")
@file:JvmMultifileClass()
package org.crazyit
fun foo(){}
@file:JvmName("CUtils")
@file:JvmMultifileClass()
package org.crazyit
fun bar(){}
编译将生成一个org.crazyit.Cutils类,该类将包含着两个静态方法;
3、类变量
伴生对象用于为其所在类模拟静态成员,注意只是模拟,伴生对象的成员依然是伴生对象本身的实例成员,并不属于伴生对象所在的类;使用@JvmStatic注解系统会为其所在类真正生成静态成员;
在对象声明或伴生对象中声明的属性会在***该对象声明或包含该伴生对象的类中***具有静态幕后字段(类变量),但这些类变量通常是private修饰,可以通过:
@JvmFeild
使用lateinit
使用const修饰
三种方式将类变量暴露出来,这样类变量就变成了与该属性具有相同访问控制符:public;
-
在java中局可以通过类名.变量访问该类变量了
class MyClass{
companion object MyObject{
@JvmField public val name=“leslie”
}}
伴生对象的属性实际上就相当于MyClass的类变量,但是它默认是private的访问权限。使用@JvmField修饰,这样改类变量就变成了与该属性具有相同的访问控制符:public
因此,在Java中就可以直接访问该类变量:
MyClass.name
-
在对象声明或伴生对象中的延迟初始化属性,具有该属性的setter方法相同的访问控制符的类变量;因为java不支持对象声明,kotlin中的对象声明在java中表现为一个单例类对象声明的属性就成了类变量;有由于lateinit属性需要等到使用时才赋值,因此Kotlin必须将该变量暴露出来,否则java将无法访问到对该类变量赋值;
object MyObject{ lateinit var name:String }
该对象声明中延迟属性将会暴露成类变量,在java中:
MyObject.name="leslie"
- kotlin中使用const修饰的属性,不管在顶层定义的还是类中定义的,都会变成使用public static final 修饰的类变量;
4、暴露实例变量
Kotlin允许将属性暴露成实例变量,使用@JvmFeild修饰该属性,暴露出的属性将会和kotlin中的属性具有相同的访问权限
@JvmFeild将属性暴露成实例变量要求:
- 该属性具有幕后字段
- 非private修饰符
- 不能使用open、override、const修饰
- 不属于委托属性
5、类方法
在***对象声明或者伴生对象中定义的方法转化成类方法***,如果这些方法使用@JvmStatic修饰的话,编译器即会在对象声明或伴生对象中生成实例方法,也会在对象声明(单例)、包含该伴生对象的类中生成类方法;
object MyObject{
fun test(){
}
JvmStatic fun foo(){}
}
class MyClass{
companion object{
fun test(){}
@JvmStatic dun output(){}
}
}
Java中调用
MyObject.INSTANCE.foo()
MyObject.foo()
MyClass.Companion.output()
MyClass.output()
6、访问控制符
kotlin与Java访问控制符对应关系:
private成员:依然编译成java的private
protected:依然编译成protected,但是java中该访问符可以被同一个包中的其他成员访问,kotlin不行,只能当前类及其子类成员访问
internal;会编译成java的public,但编译成java类是会通过名字修饰来避免在java中被意外使用到;(但是编译后发现是private???)
public:依然变异成public
7、KClass获取
在kotlin中获取Java的Class对象,必须通过调用Class.kotlin扩展属性的等价形式来实现;
在java中获取koltin中的KClass对象
8、生成重载
Kotlin为方法形参提供了默认值来避免函数重载过多的问题。对于这种有默认值的方法或者函数,编译器默认只生成一个方法:默认带所有参数的方法
为了让编译器为带参数默认值的方法生成多个重载方法,可考虑使用@JvmOverloads
注意:@JvmOverloads注解使用于构造器、静态方法等,但是不适用抽象方法,包括在接口中定义的方法;
如:
@JvmOverloads
fun test(name:String,len:Int=1,price:Double=2.2){
}
编译成java:
public final class MethodCallKt {
@JvmOverloads
public static final void test(@NotNull String name, int len, double price) {
Intrinsics.checkParameterIsNotNull(name, "name");
}
// $FF: synthetic method
@JvmOverloads
public static void test$default(String var0, int var1, double var2, int var4, Object var5) {
if ((var4 & 2) != 0) {
var1 = 1;
}
if ((var4 & 4) != 0) {
var2 = 2.2D;
}
test(var0, var1, var2);
}
@JvmOverloads
public static final void test(@NotNull String name, int len) {
test$default(name, len, 0.0D, 4, (Object)null);
}
@JvmOverloads
public static final void test(@NotNull String name) {
test$default(name, 0, 0.0D, 6, (Object)null);
}
}
将生成三个重载方法;
9、checked异常
Kotlin没有checked异常,因此Kotlin无须使用throws抛出异常,这样即使kotlin的方法或者函数本身可能抛出checked异常(kotlin中的异常都是unchecked异常,即使这些异常在java中是checked异常,到了kotlin中都是unckecked异常),Java调用该方法或者函数时编译器也不会检查该异常;
如:
public void test{
new FileInputStream("a.txt");
}
在java中必须处理可能的IOException异常;
fun test{
FileInputStream("a.txt");
}
但是在kotlin中没有不需要任何处理。如果想在java中调用该方法,编译器会检查该IOException异常,则可以使用@Throws注解修饰该函数;
@Throws
fun foo(){
FileInputStream("a.txt")
}
这样该函数就相当于声明了throws IOException,java调用时程序就必需要么捕获该异常,要么显示抛出该异常;
10、泛型的型变
Java不支持声明处型变,只支持使用处型变,因此kotlin的声明处型变必需转化为java的使用处型变;其规则:
- 对于协变类型的泛型类Bar 把它当做参数出现时,Kotlin会自动将Bar类型的参数替换成Bar<? extends Base>
- 对于逆变类型的泛型类Foo 把它当做参数出现时,Koltin会自动将Foo类型的参数替换成Foo<? super Sub>
- 不管是逆变还协变的泛型类,当他作为返回值时,kotlin不会生成通配符;
除了默认的转换规则外,kotlin还可以使用注解控制是否生成通配符:
@JvmWildcard:该注解可指定在编译器默认不生成通配符的地方强制生成通配符;
@JvmSuppressWildcards:该注解可指定在编译器默认生成通配符的地方强制不生成通配符;这个注解除了可以修饰方法函数形参的泛型形参,还可以修饰整个函数或方法(位于方法或函数最开始)、类从而阻止整个方法或者类的声明处型变生成通配符;
如:
open class Father
class Sub:Father()
class Box<out T>(val value:T)
fun boxSub(value:Sub):Box<Sub> =Box(value)
fun boxBase(box:Box<Father>):Father=box.value
//fun boxBase(box:Box<@JvmSuppressWildcards Father>):Father=box.value
java中调用该这两个方法时:
这两个方法签名为:
public static final Box<Sub> boxSub(Sub)
public static final Father boxBase(Box<? extends Father>)