kotlin笔记 十二 (一)java与Kotlin互调

这篇博客详细探讨了Java与Kotlin之间的互调,包括Kotlin调用Java的属性、关键字处理、数组、SAM转换、泛型转换等方面,以及Java调用Kotlin的属性、包级函数、类变量等。内容涵盖了两者互调的关键点和注意事项,帮助开发者理解如何在两者间无缝切换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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中是通过以下两种方式:

  1. obj::class.java
  2. 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将属性暴露成实例变量要求:

  1. 该属性具有幕后字段
  2. 非private修饰符
  3. 不能使用open、override、const修饰
  4. 不属于委托属性

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的使用处型变;其规则:

  1. 对于协变类型的泛型类Bar 把它当做参数出现时,Kotlin会自动将Bar类型的参数替换成Bar<? extends Base>
  2. 对于逆变类型的泛型类Foo 把它当做参数出现时,Koltin会自动将Foo类型的参数替换成Foo<? super Sub>
  3. 不管是逆变还协变的泛型类,当他作为返回值时,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>)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值