当前位置:   article > 正文

Kotlin与java的互操作-Kotlin在Android中的使用(三)_android java 类中使用kotlin语法

android java 类中使用kotlin语法

一、Java调用Kotlin

1.对象

java调用Kotlin的对象,按照java的语法直接new出来操作即可,不同的是,属性的set方法只适用于var修饰的属性。

2.实例字段

如果需要在 Java 中将 Kotlin 属性作为字段暴露,那就使用 @JvmField 注解对其标注。 该字段将具有与底层属性相同的可见性。如果一个属性有幕后字段(backing field)、非私有、没有 open /override 或者 const 修饰符并且不是被委托的属性,那么你可以用 @JvmField 注解该属性。

class User(id: String) {
    @JvmField val ID = id
}
  • 1
  • 2
// Java
class JavaClient {
    public String getID(User user) {
        return user.ID;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

3.静态字段

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

通常这些字段是私有的,但可以通过以下方式之一暴露出来:

— @JvmField 注解;
— lateinit 修饰符;
— const 修饰符。
使用 @JvmField 标注这样的属性使其成为与属性本身具有相同可见性的静态字段。

class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
// Java
Key.COMPARATOR.compare(key1, key2);
// Key 类中的 public static final 字段
  • 1
  • 2

在命名对象或者伴生对象中的一个延迟初始化的属性具有与属性 setter 相同可见性的静态幕后字段

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

(在类中以及在顶层)以 const 声明的属性在 Java 中会成为静态字段:

object Obj {
    const val CONST = 1
}

class C {
    companion object {
        const val VERSION = 9
    }
}

const val MAX = 239
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
int const = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;
  • 1
  • 2

4.静态方法

Kotlin 将包级函数表示为静态方法。 Kotlin 还可以为命名对象或伴生对象中定义的函数生成静态方法,如果你将这些函数标注为 @JvmStatic 的话。 如果你使用该注解,编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。 例如:

class C {
    companion object {
        @JvmStatic fun callStatic() {}
        fun callNonStatic() {}
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

现在,callStatic() 在 Java 中是静态的,而 callNonStatic() 不是:

C.callStatic(); // 没问题
C.callNonStatic(); // 错误:不是一个静态方法
C.Companion.callStatic(); // 保留实例方法
C.Companion.callNonStatic(); // 唯一的工作方式
  • 1
  • 2
  • 3

对于命名对象也同样:

object Obj {
    @JvmStatic fun callStatic() {}
    fun callNonStatic() {}
}
  • 1
  • 2
  • 3

java中:

Obj.callStatic(); // 没问题
Obj.callNonStatic(); // 错误
Obj.INSTANCE.callNonStatic(); // 没问题,通过单例实例调用
Obj.INSTANCE.callStatic(); // 也没问题
  • 1
  • 2
  • 3

5.可见性

Kotlin 的可见性以下列方式映射到 Java:

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

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

kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)

    6.生成重载

    通常,如果你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见,如果希望向 Java 调用者暴露多个重载,可以使用 @JvmOverloads 注解。
    该注解也适用于构造函数、静态方法等。它不能用于抽象方法,包括在接口中定义的方法。

    class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
        @JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*……*/ }
    }
    • 1
    • 2

    对于每一个有默认值的参数,都会生成一个额外的重载,这个重载会把这个参数和它右边的所有参数都移除掉。在上例中,会生成以下代码 :

    // 构造函数:
    Circle(int centerX, int centerY, double radius)
    Circle(int centerX, int centerY)
    
    // 方法
    void draw(String label, int lineWidth, String color) { }
    void draw(String label, int lineWidth) { }
    void draw(String label) { }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果一个类的所有构造函数参数都有默认值,那么会为其生成一个公有的无参构造函数。这就算没有 @JvmOverloads 注解也有效。

    7.受检异常

    Kotlin 没有受检异常。 所以,通常 Kotlin 函数的 Java 签名不会声明抛出异常。 于是如果我们有一个这样的 Kotlin 函数:

    // example.kt
    package demo
    
    fun writeToFile() {
        /*...*/
        throw IOException()
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后我们想要在 Java 中调用它并捕捉这个异常:

    // Java
    try {
      demo.Example.writeToFile();
    }
    catch (IOException e) { // 错误:writeToFile() 未在 throws 列表中声明 IOException
      // ……
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因为 writeToFile() 没有声明 IOException,我们从 Java 编译器得到了一个报错消息。 为了解决这个问题,要在 Kotlin 中使用 @Throws 注解。

    @Throws(IOException::class)
    fun writeToFile() {
        /*...*/
        throw IOException()
    }
    • 1
    • 2
    • 3
    • 4

    8.空安全性

    当从 Java 中调用 Kotlin 函数时,没人阻止我们将 null 作为非空参数传递。 这就是为什么 Kotlin 给所有期望非空参数的公有函数生成运行时检测。 这样我们就能在 Java 代码里立即得到 NullPointerException。

    二、Kotlin调用Java

    1.Getter 和 Setter

    遵循 Java 约定的 getter 和 setter 的方法(名称以 get 开头的无参数方法和以 set 开头的单参数方法)在 Kotlin 中表示为属性。 Boolean 访问器方法(其中 getter 的名称以 is 开头而 setter 的名称以 set 开头)会表示为与 getter 方法具有相同名称的属性。 例如:

    import java.util.Calendar
    
    fun calendarDemo() {
        val calendar = Calendar.getInstance()
        if (calendar.firstDayOfWeek == Calendar.SUNDAY) {  // 调用 getFirstDayOfWeek()
            calendar.firstDayOfWeek = Calendar.MONDAY      // 调用ll setFirstDayOfWeek()
        }
        if (!calendar.isLenient) {                         // 调用 isLenient()
            calendar.isLenient = true                      // 调用 setLenient()
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果 Java 类只有一个 setter,它在 Kotlin 中不会作为属性可见,因为 Kotlin 目前不支持只写(set-only)属性。

    2.返回 void 的方法

    如果一个 Java 方法返回 void,那么从 Kotlin 调用时中返回 Unit。 万一有人使用其返回值,它将由 Kotlin 编译器在调用处赋值, 因为该值本身是预先知道的(是 Unit)。

    3.将 Kotlin 中是关键字的 Java 标识符进行转义

    一些 Kotlin 关键字在 Java 中是有效标识符:in、 object、 is 等等。 如果一个 Java 库使用了 Kotlin 关键字作为方法,你仍然可以通过反引号(`)字符转义它来调用该方法:

    foo.`is`(bar)

      4.空安全与平台类型

      Java 中的任何引用都可能是 null,这使得 Kotlin 对来自 Java 的对象要求严格空安全是不现实的。 Java 声明的类型在 Kotlin 中会被特别对待并称为平台类型。对这种类型的空检查会放宽, 因此它们的安全保证与在 Java 中相同
      示例:

      val list = ArrayList<String>() // 非空(构造函数结果)
      list.add("Item")
      val size = list.size // 非空(原生 int)
      val item = list[0] // 推断为平台类型(普通 Java 对象)
      • 1
      • 2
      • 3

      当我们调用平台类型变量的方法时,Kotlin 不会在编译时报告可空性错误, 但在运行时调用可能会失败,因为空指针异常或者 Kotlin 生成的阻止空值传播的断言:

      item.substring(1) // 允许,如果 item == null 可能会抛出异常

        平台类型是不可标示的,意味着不能在语言中明确地写下它们。 当把一个平台值赋值给一个 Kotlin 变量时,可以依赖类型推断(该变量会具有推断出的的平台类型, 如上例中 item 所具有的类型),或者我们可以选择我们期望的类型(可空或非空类型均可):

        val nullable: String? = item // 允许,没有问题
        val notNull: String = item // 允许,运行时可能失败
        • 1

        如果我们选择非空类型,编译器会在赋值时触发一个断言。这防止 Kotlin 的非空变量保存空值。当我们把平台值传递给期待非空值等的 Kotlin 函数时,也会触发断言。 总的来说,编译器尽力阻止空值通过程序向远传播(尽管鉴于泛型的原因,有时这不可能完全消除)。

        5.注解类型参数

        可以标注泛型类型的类型参数,以便同时为其提供可空性信息。例如,考虑这些 Java 声明的注解:

        @NotNull
        Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements) { …… }
        • 1

        在 Kotlin 中可见的是以下签名:

        fun toSet(elements: (Mutable)Collection<String>) : (Mutable)Set<String> { …… }

          请注意 String 类型参数上的 @NotNull 注解。如果没有的话,类型参数会是平台类型:

          fun toSet(elements: (Mutable)Collection<String!>) : (Mutable)Set<String!> { …… }

            标注类型参数适用于面向 Java 8 或更高版本环境,并且要求可空性注解支持 TYPE_USE 目标
            (注:由于当前的技术限制,IDE 无法正确识别用作依赖的已编译 Java 库中类型参数上的这些注解。)

            6.已映射类型

            Kotlin 特殊处理一部分 Java 类型。这样的类型不是“按原样”从 Java 加载,而是 映射 到相应的 Kotlin 类型。 映射只发生在编译期间,运行时表示保持不变。 Java 的原生类型映射到相应的 Kotlin 类型(请记住平台类型):

            Java 类型Kotlin 类型
            bytekotlin.Byte
            shortkotlin.Short
            intkotlin.Int
            longkotlin.Long
            charkotlin.Char
            floatkotlin.Float
            doublekotlin.Double
            booleankotlin.Boolean

            一些非原生的内置类型也会作映射:

            Java 类型Kotlin 类型
            java.lang.Objectkotlin.Any!
            java.lang.Cloneablekotlin.Cloneable!
            java.lang.Comparablekotlin.Comparable!
            java.lang.Enumkotlin.Enum!
            java.lang.Annotationkotlin.Annotation!
            java.lang.Deprecatedkotlin.Deprecated!
            java.lang.CharSequencekotlin.CharSequence!
            java.lang.Stringkotlin.String!
            java.lang.Numberkotlin.Number!
            java.lang.Throwablekotlin.Throwable!

            Java 的装箱原始类型映射到可空的 Kotlin 类型:

            Java 类型Kotlin 类型
            java.lang.Objectkotlin.Any!
            java.lang.Bytekotlin.Byte!
            java.lang.Shortkotlin.Short!
            java.lang.Integerkotlin.Int!
            java.lang.Longkotlin.Long!
            java.lang.CharacterLong.Character!
            java.lang.Floatkotlin.Float!
            java.lang.Doublekotlin.Double!
            java.lang.Booleankotlin.Boolean!

            用作类型参数的装箱原始类型映射到平台类型: 例如,List<java.lang.Integer> 在 Kotlin 中会成为 List<Int!>

            集合类型在 Kotlin 中可以是只读的或可变的,因此 Java 集合类型作如下映射: (下表中的所有 Kotlin 类型都驻留在 kotlin.collections包中):

            Java 类型Kotlin 只读类型Kotlin 可变类型加载的平台类型
            Iterator< T >Iterator< T >MutableIterator< T >(Mutable)Iterator< T >!
            Iterable< T >Iterable< T >MutableIterable< T >(Mutable)Iterable< T >!
            Collection< T >Collection< T >MutableCollection< T >(Mutable)Collection< T >!
            Set< T >Set< T >MutableSet< T >(Mutable)Set< T >!
            List< T >List< T >MutableList< T >(Mutable)List< T >!
            ListIterator< T >ListIterator< T >MutableListIterator< T >(Mutable)ListIterator< T >!
            Map<K, V>Map<K, V>MutableMap<K, V>(Mutable)Map<K, V>!
            Map.Entry<K, V>Map.Entry<K, V>MutableMap.MutableEntry<K,V>(Mutable)Map.(Mutable)Entry<K, V>!

            Java 的数组按下文所述映射:

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

            注意:这些 Java 类型的静态成员不能在相应 Kotlin 类型的伴生对象中直接访问。要调用它们,请使用 Java 类型的完整限定名,例如 java.lang.Integer.toHexString(foo)

            7.Kotlin 中的 Java 泛型

            Kotlin 的泛型与 Java 有点不同(参见泛型)。当将 Java 类型导入 Kotlin 时,我们会执行一些转换:

            Java 的通配符转换成类型投影,
            Foo<? extends Bar> 转换成 Foo<out Bar!>!
            Foo<? super Bar> 转换成 Foo<in Bar!>!
            Java的原始类型转换成星投影,
            List 转换成 List<*>!,即 List<out Any?>!
            和 Java 一样,Kotlin 在运行时不保留泛型,即对象不携带传递到他们构造器中的那些类型参数的实际类型。 即 ArrayList()ArrayList() 是不能区分的。 这使得执行 is-检测不可能照顾到泛型。 Kotlin 只允许 is-检测星投影的泛型类型:

            if (a is List<Int>) // 错误:无法检查它是否真的是一个 Int 列表
            // but
            if (a is List<*>) // OK:不保证列表的内容
            • 1
            • 2

            8.Java 数组

            与 Java 不同,Kotlin 中的数组是不型变的。这意味着 Kotlin 不允许我们把一个 Array 赋值给一个 Array, 从而避免了可能的运行时故障。Kotlin 也禁止我们把一个子类的数组当做超类的数组传递给 Kotlin 的方法, 但是对于 Java 方法,这是允许的(通过 Array<(out) String>! 这种形式的平台类型)。

            Java 平台上,数组会使用原生数据类型以避免装箱/拆箱操作的开销。 由于 Kotlin 隐藏了这些实现细节,因此需要一个变通方法来与 Java 代码进行交互。 对于每种原生类型的数组都有一个特化的类(IntArrayDoubleArrayCharArray 等等)来处理这种情况。 它们与 Array 类无关,并且会编译成 Java 原生类型数组以获得最佳性能。

            假设有一个接受 int 数组索引的 Java 方法:

            public class JavaArrayExample {
            
                public void removeIndices(int[] indices) {
                    // 在此编码……
                }
            }
            • 1
            • 2
            • 3
            • 4
            • 5

            在 Kotlin 中你可以这样传递一个原生类型的数组:

            val javaObj = JavaArrayExample()
            val array = intArrayOf(0, 1, 2, 3)
            javaObj.removeIndices(array)  // 将 int[] 传给方法
            • 1
            • 2

            当编译为 JVM 字节代码时,编译器会优化对数组的访问,这样就不会引入任何开销:

            val array = arrayOf(1, 2, 3, 4)
            array[1] = array[1] * 2 // 不会实际生成对 get() 和 set() 的调用
            for (x in array) { // 不会创建迭代器
                print(x)
            }
            • 1
            • 2
            • 3
            • 4

            即使当我们使用索引定位时,也不会引入任何开销:

            for (i in array.indices) {// 不会创建迭代器
                array[i] += 2
            }
            • 1
            • 2

            最后,in-检测也没有额外开销:

            if (i in array.indices) { // 同 (i >= 0 && i < array.size)
                print(array[i])
            }
            • 1
            • 2

            9.Java 可变参数

            Java 类有时声明一个具有可变数量参数(varargs)的方法来使用索引:

            public class JavaArrayExample {
            
                public void removeIndicesVarArg(int... indices) {
                    // 在此编码……
                }
            }
            • 1
            • 2
            • 3
            • 4
            • 5

            在这种情况下,你需要使用展开运算符 * 来传递 IntArray:

            val javaObj = JavaArrayExample()
            val array = intArrayOf(0, 1, 2, 3)
            javaObj.removeIndicesVarArg(*array)
            • 1
            • 2

            目前无法传递 null 给一个声明为可变参数的方法。

            10.操作符

            由于 Java 无法标记用于运算符语法的方法,Kotlin 允许具有正确名称和签名的任何 Java 方法作为运算符重载和其他约定(invoke() 等)使用。 不允许使用中缀调用语法调用 Java 方法。

            11.受检异常

            在 Kotlin 中,所有异常都是非受检的,这意味着编译器不会强迫你捕获其中的任何一个。 因此,当你调用一个声明受检异常的 Java 方法时,Kotlin 不会强迫你做任何事情:

            fun render(list: List<*>, to: Appendable) {
                for (item in list) {
                    to.append(item.toString()) // Java 会要求我们在这里捕获 IOException
                }
            }
            • 1
            • 2
            • 3
            • 4

            12.对象方法

            当 Java 类型导入到 Kotlin 中时,类型 java.lang.Object 的所有引用都成了 Any。 而因为 Any 不是平台指定的,它只声明了 toString()hashCode()equals() 作为其成员, 所以为了能用到 java.lang.Object 的其他成员,Kotlin 要用到扩展函数。

            wait()/notify()

            类型 Any 的引用没有提供 wait() 与 notify() 方法。通常不鼓励使用它们,而建议使用 java.util.concurrent。 如果确实需要调用这两个方法的话,那么可以将引用转换为 java.lang.Object:

            (foo as java.lang.Object).wait()

              getClass()

              要取得对象的 Java 类,请在类引用上使用 java 扩展属性:

              val fooClass = foo::class.java

                上面的代码使用了自 Kotlin 1.1 起支持的绑定的类引用。你也可以使用 javaClass 扩展属性:

                val fooClass = foo.javaClass

                  clone()

                  要覆盖 clone(),需要继承 kotlin.Cloneable

                  class Example : Cloneable {
                      override fun clone(): Any { …… }
                  }
                  • 1
                  • 2

                  finalize()

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

                  class C {
                      protected fun finalize() {
                          // 终止化逻辑
                      }
                  }
                  • 1
                  • 2
                  • 3
                  • 4

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

                  13.从 Java 类继承

                  在 kotlin 中,类的超类中最多只能有一个 Java 类(以及按你所需的多个 Java 接口)。

                  14.访问静态成员

                  Java 类的静态成员会形成该类的“伴生对象”。我们无法将这样的“伴生对象”作为值来传递, 但可以显式访问其成员,例如:

                  if (Character.isLetter(a)) { …… }

                    要访问已映射到 Kotlin 类型的 Java 类型的静态成员,请使用 Java 类型的完整限定名:java.lang.Integer.bitCount(foo)

                    15.Java 反射

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

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

                    16.SAM 转换

                    就像 Java 8 一样,Kotlin 支持 SAM 转换。这意味着 Kotlin 函数字面值可以被自动的转换成只有一个非默认方法的 Java 接口的实现,只要这个方法的参数类型能够与这个 Kotlin 函数的参数类型相匹配。

                    你可以这样创建 SAM 接口的实例:

                    val runnable = Runnable { println("This runs in a runnable") }

                      ……以及在方法调用中:

                      val executor = ThreadPoolExecutor()
                      // Java 签名:void execute(Runnable command)
                      executor.execute { println("This runs in a thread pool") }
                      • 1
                      • 2

                      如果 Java 类有多个接受函数式接口的方法,那么可以通过使用将 lambda 表达式转换为特定的 SAM 类型的适配器函数来选择需要调用的方法。这些适配器函数也会按需由编译器生成:

                      executor.execute(Runnable { println("This runs in a thread pool") })

                        请注意,SAM 转换只适用于接口,而不适用于抽象类,即使这些抽象类也只有一个抽象方法。

                        还要注意,此功能只适用于 Java 互操作;因为 Kotlin 具有合适的函数类型,所以不需要将函数自动转换为 Kotlin 接口的实现,因此不受支持。

                        17.在 Kotlin 中使用 JNI

                        要声明一个在本地(C 或 C++)代码中实现的函数,你需要使用 external 修饰符来标记它:

                        external fun foo(x: Int): Double

                          参考文档

                          Kotlin语言中文站

                          声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/223363
                          推荐阅读
                          相关标签
                            

                          闽ICP备14008679号