Kotlin 7 kotlin与Java交互

1,208 阅读9分钟

注:当前文档为Kotlin自学总结,若什么地方含义模糊不清,敬请指教,谢谢:-)。

目录:
	- Kotlin 中调用 Java
	- Java 中调用 Kotlin

Kotlin调用Java

  1. 类和调用 - Kotlin中可以导入Java包并调用函数.

     import java.util.*
    
     fun demo(source: List<Int>) {
         val list = ArrayList<Int>()
         // “for”-循环用于 Java 集合:
         for (item in source) {
             list.add(item)
         }
         // 操作符约定同样有效:
         for (i in 0..source.size() - 1) {
             list[i] = source[i] // 调用 get 和 set
         }
     }
    
  2. 属性和访问器 - Kotlin支持Java中的gettersetter方法,并使该参数隐式调用和赋值;不支持只存在setter方法的属性由于不支持只写的属性.

     import java.util.Calendar
     
     fun calendarDemo() {
         val calendar = Calendar.getInstance()
         if (calendar.firstDayOfWeek == Calendar.SUNDAY) {  
     		// 调用 getFirstDayOfWeek()
             calendar.firstDayOfWeek = Calendar.MONDAY       
     		// 调用 setFirstDayOfWeek()
         }
     }
    
  3. 函数

    • 函数无返回值时,返回类型为**Unit**,并由Kotlin编译器于调用处赋值;

    • 当函数名称与Kotlin关键字或标识符冲突,可以将名称由反引号" ` "引用;

        foo.`is`(bar)
      
    • Kotlin中调用**平台类型(Java声明的类型)**的函数时空检查会放宽,可能会出现运行时失败.为防止非空变量为空造成运行时失败,需要选择可空或非空类型进行数据传递并且编译器尽力阻止空值向远赋值传播.

        val list = ArrayList<String>() // 非空(构造函数结果)
        list.add("Item")
        val size = list.size() // 非空(原生 int)
        val item = list[0] // 推断为平台类型(普通 Java 对象)
        
        item.substring(1) // 允许,如果 item == null 可能会抛出异常
        
        val nullable: String? = item // 允许,没有问题
        val notNull: String = item // 允许,运行时可能失败
      
    • Kotlin调用Java需要手动抛出异常的函数时,可以不做任何操作.

  4. 平台类型标记助记符**"!"**

    编译器或IDE中使用助记符标记显示平台类型,通常可以解释为"非空或可空,可变或不可变的Java类型T".

  5. Java中明确可空注解不表示为平台类型,而表示为可空类型或非空类型.

    点此查看目标可空注解

  6. Java中基本数据类型及包装类、内置类型集合数组,在Kotlin中的转换映射

    包装类存在是否为null的情况。

    本数据类型 Kotlin 类型 包装类型 包装类的Kotlin类型 包装类集合 → Kotlin
    yte kotlin.Byte java.lang.Byte kotlin.Byte? List<Byte!>
    hort kotlin.Short java.lang.Short kotlin.Short? List<Short!>
    nt kotlin.Int java.lang.Integer kotlin.Int? List<Int!>
    ong kotlin.Long java.lang.Long kotlin.Long? List<Long!>
    har kotlin.Char java.lang.Character kotlin.Char? List<Char!>
    loat kotlin.Float java.lang.Float kotlin.Float? List<Float!>
    ouble kotlin.Double java.lang.Double kotlin.Double? List<Double!>
    oolean kotlin.Boolean java.lang.Boolean kotlin.Boolean? List<Boolean!>
    ava 类型 Kotlin 类型
    ava.lang.Object kotlin.Any!
    ava.lang.Cloneable kotlin.Cloneable!
    ava.lang.Comparable kotlin.Comparable!
    ava.lang.Enum kotlin.Enum!
    ava.lang.Annotation kotlin.Annotation!
    ava.lang.Deprecated kotlin.Deprecated!
    ava.lang.CharSequence kotlin.CharSequence!
    ava.lang.String kotlin.String!
    ava.lang.Number kotlin.Number!
    ava.lang.Throwable kotlin.Throwable!

    所有java集合的映射 → 集合类型在 Kotlin 中可以是只读的或可变的,可写需要添加**"Mutable..."**,只读不需要添加.

    ava 类型 Kotlin 只读类型 Kotlin 可变类型 加载的平台类型
    terator Iterator MutableIterator (Mutable)Iterator!
    terable Iterable MutableIterable (Mutable)Iterable!
    ollection Collection MutableCollection (Mutable)Collection!
    et Set MutableSet (Mutable)Set!
    ist List MutableList (Mutable)List!
    istIterator ListIterator MutableListIterator (Mutable)ListIterator!
    ap<K, V> Map<K, V> MutableMap<K, V> (Mutable)Map<K, V>!
    ap.Entry<K, V> Map.Entry<K, V> MutableMap.MutableEntry<K,V> (Mutable)Map.(Mutable)Entry<K, V>!

    数组的映射

    |Java 类型 |Kotlin 类型| | |int[] |kotlin.IntArray!| |String[] |kotlin.Array<(out) String>!|

  7. 泛型映射

    Kotlin运行时,对象不携带传递到构造的参数的实际类型,所以无法使用 is 关键字判断泛型类型.

     if (a is List<Int>) // 错误:无法检查它是否真的是一个 Int 列表
    

    没有泛型时:

     List → List<*>!  //List<out Any?>!
    

    泛型通配符:

     Foo<? extends Bar> 	→ 	Foo<out Bar!>!
     Foo<? super Bar> 	→ 	Foo<in Bar!>!
    
  8. 数组

    Kotlin中数组是不形变的(不允许子类型数组向上转换成基类数组),为了避免运行时的故障,对于原生(基本数据)类型数组都由特化的类,如:IntArray,DoubleArray,CharArray.(注:这些类并不是Array的子类)

  9. 可变数组传入

     //Java
     public class JavaArrayExample {
     
         public void removeIndices(int... indices) {
             // 在此编码……
         }
     }
    
     //Kotlin传入非空数组需要 " * "做标记作为可变数组传入
    
     val javaObj = JavaArray()
     val array = intArrayOf(0, 1, 2, 3)
     javaObj.removeIndicesVarArg(*array)
    
  10. 对象方法 - 当引用到Java的Object类时会变成Any,若要使用其他Object成员,需要自定义除了 toString()hashCode()equals()的扩展函数.

  11. wait()、notify()的调用需要转化成Object类:(foo as java.lang.Object).wait().

  12. 覆盖clone(),继承 kotlin.Cloneable

    	class Example : Cloneable {
    	    override fun clone(): Any { …… }
    	}		
    
  13. 获得对象的类引用getClass()

    	//当对象是kotlin类时
    	val fooClass = foo::class
    
    	//当对象是java类时
    	val fooClass = foo::class.java
    
  14. finalize()

    要覆盖 finalize(),所有你需要做的就是简单地声明它,而不需要 override 关键字:

    		class C {
    		    protected fun finalize() {
    		        // 终止化逻辑
    		    }
    		}
    

    根据 Java 的规则,finalize() 不能是 private 的

  15. kotlin类只能继承一个Java类

  16. Java类静态成员都会形成该类的伴生对象.

  17. Java 反射

    Java 反射适用于 Kotlin 类,反之亦然。如上所述,你可以使用 instance::class.java, ClassName::class.java 或者 instance.javaClass 通过 java.lang.Class 来进入 Java 反射。

    其他支持的情况包括为一个 Kotlin 属性获取一个 Java 的 getter/setter 方法或者幕后字段、为一个 Java 字段获取一个 KProperty、为一个 KFunction 获取一个 Java 方法或者构造函数,反之亦然。

  18. SAM转换

  19. Kotlin使用JNI:函数需要标记**external关键字**

    external fun foo(x: Int): Double	
    

Java 中调用 Kotlin

  1. 获取类

    • 有时你需要调用有 KClass 类型参数的 Kotlin 方法。 因为没有从 Class 到 KClass 的自动转换,所以你必须通过调用 Class.kotlin 扩展属性的等价形式来手动进行转换:

        	kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
      
  2. Kotlin属性:

    • kotlin属性及访问器 → Java的私有属性及getter,setter方法

      当kotlin属性以is开头则getter方法仍以is开头,如:

        isOpen属性的getter为isOpen(),setter为setOpen(...);
      
    • 使用 @JvmField注解对属性进行标注使具有与底层属性相同的可见性以便Java调用.

        //kt类
        class C(id: String) {
            @JvmField val ID = id
        }
        //Java调用
        class JavaClient {
            public String getID(C c) {
                return c.ID;
            }
        }
      

      属性要求:

        - 有幕后属性:使用getter.setter赋值获取值;
        - 非私有;
        - 没有open/override(没有被修改或继承修改);
        - 没有const(不是运行时常量);
      
    • 转为Java静态字段

      • 使用 @JvmField标注的伴生对象的属性 = 当前类的静态常量**(public static final)**

          class Key(val value: Int) {
              companion object {
                  @JvmField
                  val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
              }
          }
          
          // Java
          Key.COMPARATOR.compare(key1, key2);
          // Key 类中的 public static final 字段
        
      • 可写的延迟属性(lateinit var ***):(public static)

          	object Singleton {
          	    lateinit var provider: Provider
          	}
          	// Java
          	Singleton.provider = new Provider();
          	// 在 Singleton 类中的 public static 非-final 字段
        
      • 常量(const val)

          // 文件 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;
        
  3. Kotlin函数

    • 包级函数:

      • 某个包下的Kotlin类文件下声明的所有函数,属性和扩展函数都改为[包名].[类名Kt].java的静态方法;

          //Kotlin  Example.kt
          package demo
          class Foo
          fun bar() {}
        
          // Java调用
          new demo.Foo();
          demo.ExampleKt.bar();
        
      • 也可以在Kotlin类中使用注解@JvmName("新类名")生成Java类的类名;

          //Kotlin上例添加@file:JvmName("DemoUtils")
        
          //Java调用	
          new demo.Foo();
          demo.DemoUtils.bar();
        
      • 当多个指定的类名相同时,在所有相关文件中使用 @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();
        
    • 静态函数

      • 包级函数均为静态函数

      • 使用 @JvmStatic注解的命名对象或伴生对象中定义的函数;注:使用该注解的对象或伴生对象中的属性会使getter,setter方法成为静态成员函数.

          	//Kotlin伴生对象	
          	class C {
          	    companion object {
          	        @JvmStatic fun foo() {}
          	        fun bar() {}
          	    }
          	}
          	//Java
          	C.foo(); // 没问题
          	C.bar(); // 错误:不是一个静态方法
          	C.Companion.foo(); // 保留实例方法
          	C.Companion.bar(); // 唯一的工作方式
        
        
          	//Kotlin命名对象
          	object Obj {
          	    @JvmStatic fun foo() {}
          	    fun bar() {}
          	}
          	//Java
          	Obj.foo(); // 没问题
          	Obj.bar(); // 错误
          	Obj.INSTANCE.bar(); // 没问题,通过单例实例调用
          	Obj.INSTANCE.foo(); // 也没问题
        
    • 使用@JvmName解决函数冲突

      • 因为类型擦除造成的冲突

          fun List<String>.filterValid(): List<String>
          fun List<Int>.filterValid(): List<Int>
          //因为JVM签名相同,故这两个函数不能同时定义
        

        使用注解标注其中一个或两个改变函数名称, kotlin仍可以使用相同名称调用,而在java中调用函数改变之后的名称

          fun List<String>.filterValid(): List<String>
        
          @JvmName("filterValidInt")
          fun List<Int>.filterValid(): List<Int>
        
      • 使用该注解对属性访问器和其他函数解决冲突

          val x: Int
              @JvmName("getX_prop")
              get() = 15
          
          fun getX() = 10
        
    • @JvmOverloads 函数重载 - 用于kotlin重载函数在Java 代码中可见,默认在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见.适用于构造函数、静态方法等,不能用于抽象函数及接口定义函数.

      注意: 当构造函数所有参数都存在默认值,则会自动生成无参构造,没有必要使用@JvmOverloads.

    • kotlin中没有受检异常,要Java函数签名检测出有异常抛出需要@Throws注解

        // example.kt
        package demo
      
        @Throws(IOException::class)//没有注解时,catch时错误
        fun foo() {
            throw IOException()
        }
        
        
        // Java
        try {
          demo.Example.foo();
        }
        catch (IOException e) {
          // ……
        }
      
    • 通配符转换 @JvmWildcard@JvmSuppressWildcards

        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
      
      
        //kotlin中使用
        unboxBase(boxDerived("s"))
      

      Java中泛型参数是不形变的,如下,Box<Derived> 并不是 Box<Base> 的子类,故不能像kotlin一样调用.

        //转换成Java代码
        Box<Derived> boxDerived(Derived value) { …… }
        Base unboxBase(Box<Base> box) { …… }
      

      需要使用注解和通配符完成转换

        //----------------kotlin函数定义-----------------
      
        //@JvmSuppressWildcards 不生成通配符
        fun boxDerived(value: Derived): Box<@JvmSuppressWildcards Derived> = Box(value)
      
        //@JvmWildcard 生成通配符
        fun unboxBase(box: Box<@JvmWildcard Base>): Base = box.value
      
      
        //-------------------Java----------------------
        // 作为返回类型——没有通配符
        Box<Derived> boxDerived(Derived value) { …… }
         
        // 作为参数——有通配符
        Base unboxBase(Box<? extends Base> box) { …… }
      

      注: @JvmSuppressWildcards 不仅可用于单个类型参数,还可用于整个声明(如函数或类),从而抑制其中的所有通配符。

    • 函数与Nothing类型返回

      Nothing在Java中没有对应的类型,并且不能接收null,所以在Java中使用Nothing参数的地方kotlin生成一个原始类型

        	fun emptyList(): List<Nothing> = listOf()
        	// 会翻译成
        	// List emptyList() { …… }
      
    • 访问权限(Kotlin → Java)

      • private → private
      • private 的顶层声明编译成包级局部声明;
      • protected → protected,Kotlin不允许访问同一个包下其他类受保护的成员
      • internal → public,internal 类的成员会通过名字修饰,使其更难以在 Java 中意外使用到,并且根据 Kotlin 规则使其允许重载相同签名的成员而互不可见;
      • public → public