zoukankan      html  css  js  c++  java
  • Kotlin与Java互操作[文档]

    在 Kotlin 中调用 Java 代码

    Kotlin 在设计时就考虑了 Java 互操作性。可以从 Kotlin 中自然地调用现存的 Java 代码,并且在 Java 代码中也可以很顺利地调用 Kotlin 代码。在本节中我们会介绍从 Kotlin 中调用 Java 代码的一些细节。

    几乎所有 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
        }
    }
    

    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()
        }
    }
    

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

    返回 void 的方法

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

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

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

    foo.`is`(bar)
    

    空安全与平台类型

    Java 中的任何引用都可能是 null{: .keyword },这使得 Kotlin 对来自 Java 的对象要求严格空安全是不现实的。
    Java 声明的类型在 Kotlin 中会被特别对待并称为平台类型。对这种类型的空检测会放宽,
    因此它们的安全保证与在 Java 中相同(更多请参见下文)。

    考虑以下示例:

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

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

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

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

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

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

    平台类型表示法

    如上所述,平台类型不能在程序中显式表述,因此在语言中没有相应语法。
    然而,编译器和 IDE 有时需要(在错误信息中、参数信息中等)显示他们,所以我们用一个助记符来表示他们:

    • T! 表示“T 或者 T?”,
    • (Mutable)Collection<T>! 表示“可以可变或不可变、可空或不可空的 T 的 Java 集合”,
    • Array<(out) T>! 表示“可空或者不可空的 T(或 T 的子类型)的 Java 数组”

    可空性注解

    具有可空性注解的Java类型并不表示为平台类型,而是表示为实际可空或非空的
    Kotlin 类型。编译器支持多种可空性注解,包括:

    • JetBrains
      org.jetbrains.annotations 包中的 @Nullable@NotNull
    • Android(com.android.annotationsandroid.support.annotations)
    • JSR-305(javax.annotation,详见下文)
    • FindBugs(edu.umd.cs.findbugs.annotations
    • Eclipse(org.eclipse.jdt.annotation
    • Lombok(lombok.NonNull)。

    你可以在 Kotlin 编译器源代码中找到完整的列表。

    注解类型参数

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

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

    在 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 目标(org.jetbrains.annotations 15 或以上版本支持)。

    注:由于当前的技术限制,IDE 无法正确识别用作依赖的已编译 Java 库中类型参数上的这些注解。
    {:.note}

    JSR-305 支持

    已支持 JSR-305 中定义的 @Nonnull
    注解来表示 Java 类型的可空性。

    如果 @Nonnull(when = ...) 值为 When.ALWAYS,那么该注解类型会被视为非空;When.MAYBE
    When.NEVER 表示可空类型;而 When.UNKNOWN 强制类型为平台类型

    可针对 JSR-305 注解编译库,但不需要为库的消费者将注解构件(如 jsr305.jar指定为编译依赖。Kotlin 编译器可以从库中读取 JSR-305 注解,并不需要该注解出现在类路径中。

    自 Kotlin 1.1.50 起,
    也支持自定义可空限定符(KEEP-79)
    (见下文)。

    类型限定符别称(自 1.1.50 起)

    如果一个注解类型同时标注有
    @TypeQualifierNickname
    与 JSR-305 @Nonnull(或者它的其他别称,如 @CheckForNull),那么该注解类型自身将用于
    检索精确的可空性,且具有与该可空性注解相同的含义:

    @TypeQualifierNickname
    @Nonnull(when = When.ALWAYS)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyNonnull {
    }
    
    @TypeQualifierNickname
    @CheckForNull // 另一个类型限定符别称的别称
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyNullable {
    }
    
    interface A {
        @MyNullable String foo(@MyNonnull String x);
        // 在 Kotlin(严格模式)中:`fun foo(x: String): String?`
    
        String bar(List<@MyNonnull String> x);
        // 在 Kotlin(严格模式)中:`fun bar(x: List<String>!): String!`
    }
    
    类型限定符默认值(自 1.1.50 起)

    @TypeQualifierDefault
    引入应用时在所标注元素的作用域内定义默认可空性的注解

    这些注解类型应自身同时标注有 @Nonnull(或其别称)与 @TypeQualifierDefault(...)注解,
    后者带有一到多个 ElementType 值:

    • ElementType.METHOD 用于方法的返回值;
    • ElementType.PARAMETER 用于值参数;
    • ElementType.FIELD 用于字段;以及
    • ElementType.TYPE_USE(自 1.1.60 起)适用于任何类型,包括类型参数、类型参数的上界与通配符类型。

    当类型并未标注可空性注解时使用默认可空性,并且该默认值是由最内层标注有带有与所用类型相匹配的
    ElementType 的类型限定符默认注解的元素确定。

    @Nonnull
    @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
    public @interface NonNullApi {
    }
    
    @Nonnull(when = When.MAYBE)
    @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE})
    public @interface NullableApi {
    }
    
    @NullableApi
    interface A {
        String foo(String x); // fun foo(x: String?): String?
    
        @NotNullApi // 覆盖来自接口的默认值
        String bar(String x, @Nullable String y); // fun bar(x: String, y: String?): String
    
        // 由于 `@NullableApi` 具有 `TYPE_USE` 元素类型,
        // 因此认为 List<String> 类型参数是可空的:
        String baz(List<String> x); // fun baz(List<String?>?): String?
    
        // “x”参数仍然是平台类型,因为有显式
        // UNKNOWN 标记的可空性注解:
        String qux(@Nonnull(when = When.UNKNOWN) String x); // fun baz(x: String!): String?
    }
    

    注意:本例中的类型只在启用了严格模式时出现,否则仍是平台类型。参见 @UnderMigration 注解编译器配置两节。

    也支持包级的默认可空性:

    // 文件:test/package-info.java
    @NonNullApi // 默认将“test”包中所有类型声明为不可空
    package test;
    

    {:#undermigration-注解自-1160-起}

    @UnderMigration 注解(自 1.1.60 起)

    库的维护者可以使用 @UnderMigration 注解(在单独的构件 kotlin-annotations-jvm 中提供)来定义可为空性类型限定符的迁移状态。

    @UnderMigration(status = ...) 中的状态值指定了编译器如何处理 Kotlin 中注解类型的不当用法(例如,使用 @MyNullable 标注的类型值作为非空值):

    • MigrationStatus.STRICT 使注解像任何纯可空性注解一样工作,即对不当用法报错并影响注解声明内的类型在 Kotlin 中的呈现;

    • 对于 MigrationStatus.WARN,不当用法报为警告而不是错误;
      但注解声明内的类型仍是平台类型;而

    • MigrationStatus.IGNORE 则使编译器完全忽略可空性注解。

    库的维护者还可以将 @UnderMigration 状态添加到类型限定符别称与类型限定符默认值:

    @Nonnull(when = When.ALWAYS)
    @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
    @UnderMigration(status = MigrationStatus.WARN)
    public @interface NonNullApi {
    }
    
    // 类中的类型是非空的,但是只报警告
    // 因为 `@NonNullApi` 标注了 `@UnderMigration(status = MigrationStatus.WARN)`
    @NonNullApi
    public class Test {}
    

    注意:可空性注解的迁移状态并不会从其类型限定符别称继承,而是适用于默认类型限定符的用法。

    如果默认类型限定符使用类型限定符别称,并且它们都标注有 @UnderMigration,那么使用默认类型限定符的状态。

    编译器配置

    可以通过添加带有以下选项的 -Xjsr305 编译器标志来配置 JSR-305 检测:

    • -Xjsr305={strict|warn|ignore} 设置非 @UnderMigration 注解的行为。
      自定义的可空性限定符,尤其是
      @TypeQualifierDefault 已经在很多知名库中流传,而用户更新到包含 JSR-305 支持的 Kotlin 版本时可能需要平滑迁移。自 Kotlin 1.1.60 起,这一标志只影响非 @UnderMigration 注解。

    • -Xjsr305=under-migration:{strict|warn|ignore}(自 1.1.60 起)覆盖 @UnderMigration 注解的行为。
      用户可能对库的迁移状态有不同的看法:
      他们可能希望在官方迁移状态为 WARN 时报错误,反之亦然,他们可能希望推迟错误报告直到他们完成迁移。

    • -Xjsr305=@<fq.name>:{strict|warn|ignore}(自 1.1.60 起)覆盖单个注解的行为,其中 <fq.name>该注解的完整限定类名。对于不同的注解可以多次出现。这对于管理特定库的迁移状态非常有用。

    其中 strictwarnignore 值的含义与 MigrationStatus 中的相同,并且只有 strict 模式会影响注解声明中的类型在 Kotlin 中的呈现。

    注意:内置的 JSR-305 注解 @Nonnull@Nullable@CheckForNull 总是启用并影响所注解的声明在 Kotlin 中呈现,无论如何配置编译器的 -Xjsr305 标志。

    例如,将 -Xjsr305=ignore -Xjsr305=under-migration:ignore -Xjsr305=@org.library.MyNullable:warn 添加到编译器参数中,会使编译器对由
    @org.library.MyNullable 标注的不当用法生成警告,而忽略所有其他 JSR-305 注解。

    对于 kotlin 1.1.50+/1.2 版本,其默认行为等同于 -Xjsr305=warn
    strict 值应认为是实验性的(以后可能添加更多检测)。

    已映射类型

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

    Java 类型 Kotlin 类型
    byte kotlin.Byte
    short kotlin.Short
    int kotlin.Int
    long kotlin.Long
    char kotlin.Char
    float kotlin.Float
    double kotlin.Double
    boolean kotlin.Boolean

    {:.zebra}

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

    Java 类型 Kotlin 类型
    java.lang.Object kotlin.Any!
    java.lang.Cloneable kotlin.Cloneable!
    java.lang.Comparable kotlin.Comparable!
    java.lang.Enum kotlin.Enum!
    java.lang.Annotation kotlin.Annotation!
    java.lang.CharSequence kotlin.CharSequence!
    java.lang.String kotlin.String!
    java.lang.Number kotlin.Number!
    java.lang.Throwable kotlin.Throwable!

    {:.zebra}

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

    Java type Kotlin type
    java.lang.Byte kotlin.Byte?
    java.lang.Short kotlin.Short?
    java.lang.Integer kotlin.Int?
    java.lang.Long kotlin.Long?
    java.lang.Character kotlin.Char?
    java.lang.Float kotlin.Float?
    java.lang.Double kotlin.Double?
    java.lang.Boolean kotlin.Boolean?

    {:.zebra}

    请注意,用作类型参数的装箱原始类型映射到平台类型:
    例如,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>!

    {:.zebra}

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

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

    {:.zebra}

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

    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<Integer>()ArrayList<Character>() 是不能区分的。
    这使得执行 is{: .keyword }-检测不可能照顾到泛型。
    Kotlin 只允许 is{: .keyword }-检测星投影的泛型类型:

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

    Java 数组

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

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

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

    public class JavaArrayExample {
    
        public void removeIndices(int[] indices) {
            // 在此编码……
        }
    }
    

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

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

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

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

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

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

    最后,in{: .keyword }-检测也没有额外开销:

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

    Java 可变参数

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

    public class JavaArrayExample {
    
        public void removeIndicesVarArg(int... indices) {
            // 在此编码……
        }
    }
    

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

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

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

    操作符

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

    受检异常

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

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

    对象方法

    当 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 { …… }
    }
    

    不要忘记《Effective Java》第三版 的第 13 条: 谨慎地改写clone

    finalize()

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

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

    根据 Java 的规则,finalize() 不能是 private{: .keyword } 的。

    从 Java 类继承

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

    访问静态成员

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

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

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

    Java 反射

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

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

    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") }
    

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

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

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

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

    在 Kotlin 中使用 JNI

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

    external fun foo(x: Int): Double
    

    其余的过程与 Java 中的工作方式完全相同。

    Java 中调用 Kotlin

    Java 可以轻松调用 Kotlin 代码。
    例如,可以在 Java 方法中无缝创建与操作 Kotlin 类的实例。
    然而,在将 Kotlin 代码集成到 Java 中时,
    需要注意 Java 与 Kotlin 之间的一些差异。
    在本页,我们会描述定制 Kotlin 代码与其 Java 客户端的互操作的方法。

    属性

    Kotlin 属性会编译成以下 Java 元素:

    • 一个 getter 方法,名称通过加前缀 get 算出;
    • 一个 setter 方法,名称通过加前缀 set 算出(只适用于 var 属性);
    • 一个私有字段,与属性名称相同(仅适用于具有幕后字段的属性)。

    例如,var firstName: String 编译成以下 Java 声明:

    private String firstName;
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    

    如果属性的名称以 is 开头,则使用不同的名称映射规则:getter 的名称与属性名称相同,并且 setter 的名称是通过将 is 替换为 set 获得。
    例如,对于属性 isOpen,其 getter 会称做 isOpen(),而其 setter 会称做 setOpen()
    这一规则适用于任何类型的属性,并不仅限于 Boolean

    包级函数

    org.example 包内的 app.kt 文件中声明的所有的函数和属性,包括扩展函数,
    都编译成一个名为 org.example.AppKt 的 Java 类的静态方法。

    // app.kt
    package org.example
    
    class Util
    
    fun getTime() { /*……*/ }
    
    
    // Java
    new org.example.Util();
    org.example.AppKt.getTime();
    

    可以使用 @JvmName 注解修改生成的 Java 类的类名:

    @file:JvmName("DemoUtils")
    
    package org.example
    
    class Util
    
    fun getTime() { /*……*/ }
    
    
    // Java
    new org.example.Util();
    org.example.DemoUtils.getTime();
    

    如果多个文件中生成了相同的 Java 类名(包名相同并且类名相同或者有相同的
    @JvmName 注解)通常是错误的。然而,编译器能够生成一个单一的 Java 外观类,它具有指定的名称且包含来自所有文件中具有该名称的所有声明。
    要启用生成这样的外观,请在所有相关文件中使用 @JvmMultifileClass 注解。

    // oldutils.kt
    @file:JvmName("Utils")
    @file:JvmMultifileClass
    
    package org.example
    
    fun getTime() { /*……*/ }
    
    // newutils.kt
    @file:JvmName("Utils")
    @file:JvmMultifileClass
    
    package org.example
    
    fun getDate() { /*……*/ }
    
    // Java
    org.example.Utils.getTime();
    org.example.Utils.getDate();
    

    实例字段

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

    class User(id: String) {
        @JvmField val ID = id
    }
    
    // Java
    class JavaClient {
        public String getID(User user) {
            return user.ID;
        }
    }
    

    [延迟初始化的]https://www.kotlincn.net/docs/reference/(properties.html#延迟初始化属性与变量)属性(在Java中)也会暴露为字段。
    该字段的可见性与 lateinit 属性的 setter 相同。

    静态字段

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

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

    • @JvmField 注解;
    • lateinit 修饰符;
    • const 修饰符。

    使用 @JvmField 标注这样的属性使其成为与属性本身具有相同可见性的静态字段。

    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 字段
    

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

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

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

    // 文件 example.kt
    
    object Obj {
        const val CONST = 1
    }
    
    class C {
        companion object {
            const val VERSION = 9
        }
    }
    
    const val MAX = 239
    

    在 Java 中:

    int const = Obj.CONST;
    int max = ExampleKt.MAX;
    int version = C.VERSION;
    

    静态方法

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

    class C {
        companion object {
            @JvmStatic fun callStatic() {}
            fun callNonStatic() {}
        }
    }
    

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

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

    对于具名对象也同样:

    object Obj {
        @JvmStatic fun callStatic() {}
        fun callNonStatic() {}
    }
    

    在 Java 中:

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

    自 Kotlin 1.3 起,@JvmStatic 也适用于在接口的伴生对象中定义的函数。
    这类函数会编译为接口中的静态方法。请注意,接口中的静态方法是 Java 1.8 中引入的,
    因此请确保使用相应的编译目标。

    interface ChatBot {
        companion object {
            @JvmStatic fun greet(username: String) {
                println("Hello, $username")
            }
        }
    }
    

    @JvmStatic 注解也可以应用于对象或伴生对象的属性,
    使其 getter 和 setter 方法在该对象或包含该伴生对象的类中是静态成员。

    接口中的默认方法

    默认方法仅适用于面向 JVM 1.8 及更高版本。
    {:.note}

    @JvmDefault 注解在 Kotlin 1.3 中是实验性的。其名称与行为都可能发生变化,导致将来不兼容。
    {:.note}

    自 JDK 1.8 起,Java 中的接口可以包含默认方法
    可以将 Kotlin 接口的非抽象成员为实现它的 Java 类声明为默认。
    如需将一个成员声明为默认,请使用 @JvmDefault 注解标记之。
    这是一个带有默认方法的 Kotlin 接口的一个示例:

    interface Robot {
        @JvmDefault fun move() { println("~walking~") }
        fun speak(): Unit
    }
    

    默认实现对于实现该接口的 Java 类都可用。

    //Java 实现
    public class C3PO implements Robot {
        // 来自 Robot 的 move() 实现隐式可用
        @Override
        public void speak() {
            System.out.println("I beg your pardon, sir");
        }
    }
    
    C3PO c3po = new C3PO();
    c3po.move(); // 来自 Robot 接口的默认实现
    c3po.speak();
    

    接口的实现者可以覆盖默认方法。

    //Java
    public class BB8 implements Robot {
        //自己实现默认方法
        @Override
        public void move() {
            System.out.println("~rolling~");
        }
    
        @Override
        public void speak() {
            System.out.println("Beep-beep");
        }
    }
    

    为了让 @JvmDefault 生效,编译该接口必须带有 -Xjvm-default 参数。
    根据添加注解的情况,指定下列值之一:

    • -Xjvm-default=enabled 只添加带有 @JvmDefault 注解的新方法时使用。
      这包括为 API 添加整个接口。
    • -Xjvm-default=compatibility@JvmDefault 添加到以往 API 中就有的方法时使用。
      这种模式有助于避免兼容性破坏:为先前版本编写的所有接口实现都会与新版本完全兼容。
      然而,兼容模式可能会增大生成字节码的规模并且影响性能。

    关于兼容性的更多详情请参见 @JvmDefault 参考页

    在委托中使用

    请注意,如果将带有 @JvmDefault 的方法的接口用作委托
    那么即是实际的委托类型提供了自己的实现,也会调用默认方法的实现。

    interface Producer {
        @JvmDefault fun produce() {
            println("interface method")
        }
    }
    
    class ProducerImpl: Producer {
        override fun produce() {
            println("class method")
        }
    }
    
    class DelegatedProducer(val p: Producer): Producer by p {
    }
    
    fun main() {
        val prod = ProducerImpl()
        DelegatedProducer(prod).produce() // 输出“interface method”
    }
    

    关于 Kotlin 中接口委托的更多详情,请参见委托

    可见性

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

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

    KClass

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

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

    @JvmName 解决签名冲突

    有时我们想让一个 Kotlin 中的具名函数在字节码中有另外一个 JVM 名称。
    最突出的例子是由于类型擦除引发的:

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

    这两个函数不能同时定义,因为它们的 JVM 签名是一样的:filterValid(Ljava/util/List;)Ljava/util/List;
    如果我们真的希望它们在 Kotlin 中用相同名称,我们需要用 @JvmName 去标注其中的一个(或两个),并指定不同的名称作为参数:

    fun List<String>.filterValid(): List<String>
    
    @JvmName("filterValidInt")
    fun List<Int>.filterValid(): List<Int>
    

    在 Kotlin 中它们可以用相同的名称 filterValid 来访问,而在 Java 中,它们分别是 filterValidfilterValidInt

    同样的技巧也适用于属性 x 和函数 getX() 共存:

    val x: Int
        @JvmName("getX_prop")
        get() = 15
    
    fun getX() = 10
    

    如需在没有显式实现 getter 与 setter 的情况下更改属性生成的访问器方法的名称,可以使用 @get:JvmName@set:JvmName

    @get:JvmName("x")
    @set:JvmName("changeX")
    var x: Int = 23
    

    生成重载

    通常,如果你写一个有默认参数值的 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") { /*……*/ }
    }
    

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

    // 构造函数:
    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) { }
    

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

    受检异常

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

    // example.kt
    package demo
    
    fun writeToFile() {
        /*……*/
        throw IOException()
    }
    

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

    // Java
    try {
      demo.Example.writeToFile();
    }
    catch (IOException e) { // 错误:writeToFile() 未在 throws 列表中声明 IOException
      // ……
    }
    

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

    @Throws(IOException::class)
    fun writeToFile() {
        /*……*/
        throw IOException()
    }
    

    空安全性

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

    型变的泛型

    当 Kotlin 的类使用了声明处型变,有两种选择可以从 Java 代码中看到它们的用法。让我们假设我们有以下类和两个使用它的函数:

    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
    

    一种看似理所当然地将这俩函数转换成 Java 代码的方式可能会是:

    Box<Derived> boxDerived(Derived value) { …… }
    Base unboxBase(Box<Base> box) { …… }
    

    问题是,在 Kotlin 中我们可以这样写 unboxBase(boxDerived("s")),但是在 Java 中是行不通的,因为在 Java 中Box 在其泛型参数 T 上是不型变的,于是 Box<Derived> 并不是 Box<Base> 的子类。
    要使其在 Java 中工作,我们按以下这样定义 unboxBase

    Base unboxBase(Box<? extends Base> box) { …… }
    

    这里我们使用 Java 的通配符类型? extends Base)来通过使用处型变来模拟声明处型变,因为在 Java 中只能这样。

    当它作为参数出现时,为了让 Kotlin 的 API 在 Java 中工作,对于协变定义的 Box 我们生成 Box<Super> 作为 Box<? extends Super>
    (或者对于逆变定义的 Foo 生成 Foo<? super Bar>)。当它是一个返回值时,
    我们不生成通配符,因为否则 Java 客户端将必须处理它们(并且它违反常用
    Java 编码风格)。因此,我们的示例中的对应函数实际上翻译如下:

    // 作为返回类型——没有通配符
    Box<Derived> boxDerived(Derived value) { …… }
     
    // 作为参数——有通配符
    Base unboxBase(Box<? extends Base> box) { …… }
    

    当参数类型是 final 时,生成通配符通常没有意义,所以无论在什么地方 Box<String>始终转换为 Box<String>
    {:.note}

    如果我们在默认不生成通配符的地方需要通配符,我们可以使用 @JvmWildcard 注解:

    fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
    // 将被转换成
    // Box<? extends Derived> boxDerived(Derived value) { …… }
    

    另一方面,如果我们根本不需要默认的通配符转换,我们可以使用@JvmSuppressWildcards

    fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
    // 会翻译成
    // Base unboxBase(Box<Base> box) { …… }
    

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

    Nothing 类型翻译

    类型 Nothing 是特殊的,因为它在 Java 中没有自然的对应。确实,每个 Java 引用类型,包括
    java.lang.Void 都可以接受 null 值,但是 Nothing 不行。因此,这种类型不能在 Java 世界中准确表示。这就是为什么在使用 Nothing 参数的地方 Kotlin 生成一个原始类型:

    fun emptyList(): List<Nothing> = listOf()
    // 会翻译成
    // List emptyList() { …… }
    
  • 相关阅读:
    Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx  Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
    Atitit s2018.5 s5 doc list on com pc.docx  v2
    Atitit s2018.5 s5 doc list on com pc.docx  Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 18821766710 attilax main num.docx Atiitt put post 工具 开发工具dev tool test.docx Atiitt 腾讯图像分类相册管家.docx
    Atitit s2018 s4 doc list dvchomepc dvccompc.docx .docx s2018 s4 doc compc dtS44 s2018 s4 doc dvcCompc dtS420 s2018 s4f doc homepc s2018 s4 doc compc dtS44(5 封私信 _ 44 条消息)WebSocket 有没有可能取代 AJAX
    Atitit s2018 s3 doc list alldvc.docx .docx s2018 s3f doc compc s2018 s3f doc homepc sum doc dvcCompc dtS312 s2018 s3f doc compcAtitit PathUtil 工具新特性新版本 v8 s312.docx s2018 s3f doc compcAtitit 操作日
    Atitit s2018.2 s2 doc list on home ntpc.docx  Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx Atiitt 手写文字识别 讯飞科大 语音云.docx Atitit 代码托管与虚拟主机.docx Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx Atitit 几大研发体系对比 Stage-Gat
    Atitit 文员招募规范 attilax总结
    Atitit r2017 r6 doc list on home ntpc.docx
    atitit r9 doc on home ntpc .docx
    Atitit.如何文章写好 论文 文章 如何写好论文 技术博客 v4
  • 原文地址:https://www.cnblogs.com/ElEGenT/p/13047706.html
Copyright © 2011-2022 走看看