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() { …… }
    
  • 相关阅读:
    转:Windows 7下安装CentOS双系统
    STL学习总结之<迭代器>
    转:linux静态库与动态库
    指向类成员和成员函数的指针
    STL学习总结之<仿函数>
    转:Linux Crontab 定时任务 命令详解
    转: 解决 Redhat 出现”This system is not registered with RHN”更新
    IOS 判断设备屏幕尺寸、分辨率
    IOS 文件管理共通函数整理
    IOS 编译ffmpeg For SDK6.1,模拟器、armv7、armv7s均可使用
  • 原文地址:https://www.cnblogs.com/ElEGenT/p/13047706.html
Copyright © 2011-2022 走看看