Kotlin预定义注解

702 阅读14分钟

kotlin.jvm 是 Kotlin 标准库的一个包,它包含一些注解、函数和类,用于控制 Kotlin 代码在 Java 虚拟机(JVM)上的运行和互操作。这个包主要面向开发者使用 Kotlin 编写 JVM 应用程序。

以下是 kotlin.jvm 包中一些重要的注解和类:

@Transient

@Transient 是一个 Kotlin 注解,用于表示在类的某个属性在进行序列化时应被忽略。这在你想在序列化过程中排除某些属性,如临时变量、缓存数据或其他非重要信息时非常有用。这个注解对于 Kotlin 与 Java 的互操作性也很重要,特别是在与 Java 序列化库(如 Gson、Jackson 或 kotlinx.serialization 等)一起使用时。

要在属性上使用 @Transient 注解,请在属性声明之前加上 @Transient

例如,假设你有一个 Person 类,其中有一个 age 属性和一个 isAdult 属性。isAdult 属性是根据 age 计算出来的,因此在序列化 Person 对象时,你可能不希望将 isAdult 属性包含在序列化数据中。为了实现这一点,你可以使用 @Transient 注解:

import java.io.Serializable

data class Person(val name: String, val age: Int) : Serializable {
    @Transient
    val isAdult: Boolean
        get() = age >= 18
}

在这个示例中,Person 类实现了 Serializable 接口,这意味着它可以被 Java 序列化。然而,isAdult 属性被标记为 @Transient,因此在序列化过程中将被忽略。

注意: @Transient 注解不能用于 Kotlin 的 var 属性,因为它们具有可变状态,不能在反序列化时安全地重建。此外,不同的序列化库可能需要其他注解或配置以实现相同的效果。确保检查所使用的库的文档以获取适当的方法。

@Volatile

@Volatile 是一个 Kotlin 注解,用于表示类的某个属性在多线程环境下应具有易变性,即它应当确保在多个线程访问时的可见性。它类似于 Java 中的 volatile 关键字。这意味着当一个线程修改了被 @Volatile 注解标记的变量的值,其他线程能立即看到这个变化。

@Volatile 通常用于实现简单的线程安全功能,比如双重检查锁定(double-checked locking)模式中的单例(singleton)实现。然而,对于更复杂的多线程操作,你可能需要使用其他同步机制,如 synchronized 关键字、MutexSemaphore 等。

要在属性上使用 @Volatile 注解,请在属性声明之前加上 @Volatile

下面是一个使用 @Volatile 注解的简单示例:

class Counter {
    @Volatile
    private var count = 0

    fun increment() {
        count++
    }

    fun getCount(): Int {
        return count
    }
}

在这个示例中,count 属性被标记为 @Volatile,这意味着在多个线程访问时,它的值更改将立即对其他线程可见。然而,请注意,尽管 @Volatile 可以确保可见性,但它不能确保原子性。在此示例中,increment 方法仍然可能会出现竞态条件,因为它不是原子操作。为了确保原子性,可以使用 AtomicInteger 或其他同步机制。

@Throws

@Throws 是一个 Kotlin 注解,用于表示一个函数可能抛出特定类型的异常。这对于在 Kotlin 中与 Java 代码进行交互非常有用,因为 Kotlin 不强制进行异常检查,而 Java 需要对受检异常(checked exceptions)进行处理。使用 @Throws 注解可以向 Java 调用者提供异常类型信息,从而使得在 Java 代码中可以正确处理 Kotlin 函数抛出的异常。

@Throws 注解接受一个或多个异常类作为参数。要在函数上使用 @Throws 注解,请在函数声明之前加上 @Throws,并传入可能抛出的异常类型。

下面是一个使用 @Throws 注解的简单示例:

import java.io.IOException

class FileReader {
    @Throws(IOException::class)
    fun readFile(filename: String): String {
        // 读取文件并返回其内容,可能抛出 IOException
    }
}

在这个示例中,readFile 函数被请注意, @Throws 注解只会影响 Kotlin 函数在 Java 代码中的表现形式,对于 Kotlin 代码调用该函数,该注解没有任何影响。标记为可能抛出 IOException。当从 Java 代码中调用此函数时,将需要进行异常处理,例如使用 try-catch 语句。

@Synchronized

@Synchronized 是一个 Kotlin 注解,它用于表示某个方法在多线程环境中是同步的。这意味着,每次只有一个线程可以访问这个方法。该注解相当于 Java 中在方法上使用 synchronized 关键字。

@Synchronized 注解用于确保对共享资源的访问是互斥的,从而防止出现数据竞争和不一致的问题。当一个方法被标记为 @Synchronized 时,它会自动在方法执行期间锁定其所属对象(实例方法)或类(静态方法)。

在 Kotlin 中使用 @Synchronized 注解的示例:

class Counter {
    private var count = 0

    @Synchronized
    fun increment() {
        count++
    }

    @Synchronized
    fun getCount(): Int {
        return count
    }
}

在这个示例中,incrementgetCount 方法都被标记为 @Synchronized。这意味着,在多线程环境中,这些方法的访问是互斥的,确保 count 变量的更新和读取操作不会发生数据竞争。

请注意,尽管 @Synchronized 注解可以提高线程安全性,但它可能会降低性能,因为它会导致线程阻塞等待锁。在处理多线程问题时,还可以使用其他机制,如原子变量、线程安全的集合和显式锁等。

@JvmRecord

@JvmRecord 是一个 Kotlin 注解,用于指示 Kotlin 编译器生成一个 Java 记录类(record class)。Java 记录类是自 Java 16 起引入的一种新的类类型,用于创建简单的不可变数据持有类。记录类自动生成了很多常见的方法,例如 equals()hashCode()toString() ,使得它们在创建简单的数据类时非常有用。

要使用 @JvmRecord 注解,需要在 Kotlin 中创建一个 data class,并在类声明之前添加 @JvmRecord 注解。这将指示 Kotlin 编译器为这个数据类生成一个 Java 记录类,而不是一个普通的 Java 类。

以下是一个使用 @JvmRecord 注解的示例:

@JvmRecord
data class Person(val name: String, val age: Int)

在这个例子中, @JvmRecord 注解应用于 Person 数据类。这意味着 Kotlin 编译器在生成 Java 字节码时,会为这个数据类生成一个 Java 记录类,而不是一个普通的 Java 类。这允许 Person 类在 Java 代码中具有与 Java 记录类相同的特性。

需要注意的是,使用 @JvmRecord 注解时,必须遵循以下规则:

  1. 类必须是一个数据类。
  2. 类不能有类型参数。
  3. 类不能继承其他类(除了 Any)。
  4. 类的所有属性必须在主构造函数中声明,并且它们的可见性必须为 val

同时,请确保使用的是支持 Java 记录类的 JVM 版本(Java 16 及更高版本)。

@JvmPackageName

@JvmPackageName 是一个 Kotlin 注解,用于更改 Kotlin 生成的 Java 字节码中包的名称。在某些情况下,您可能希望为 Kotlin 生成的 Java 类使用与 Kotlin 包名称不同的包名称。例如,当您需要解决包级别名称冲突时,这可能是有用的。

要使用 @JvmPackageName 注解,需要在 Kotlin 文件的顶部添加注解,并在注解中指定所需的 Java 包名称。

以下是一个使用 @JvmPackageName 注解的示例:

@file:JvmPackageName("com.example.myjavapackage")

package com.example.mykotlinpackage

class MyClass {
    //...
}

在这个例子中, @JvmPackageName 注解用于指定 Kotlin 生成的 Java 字节码中 MyClass 的包名称应为 com.example.myjavapackage,而不是 com.example.mykotlinpackage

需要注意的是,当使用 @JvmPackageName 时,可能会导致与其他 Kotlin 或 Java 文件中的类之间的可见性问题。因此,在使用此注解时要格外小心。

@JvmOverloads

@JvmOverloads 是一个 Kotlin 注解,它用于在 Kotlin 生成的 Java 字节码中生成重载函数。在 Kotlin 中,可以为函数参数提供默认值,从而允许在调用函数时省略这些参数。然而,在 Java 中,这种语法特性不存在。为了在 Java 代码中方便地使用这些 Kotlin 函数,可以使用 @JvmOverloads 注解。

当使用 @JvmOverloads 注解时,Kotlin 编译器会为每个具有默认值的参数生成一个新的重载函数,这样 Java 代码就可以像在 Kotlin 中一样选择性地提供参数。在 Java 中调用这些函数时,省略的参数将使用它们在 Kotlin 中定义的默认值。

以下是一个使用 @JvmOverloads 注解的示例:

class MyClass {
    @JvmOverloads
    fun greet(name: String = "World", exclamation: String = "!") {
        println("Hello, $name$exclamation")
    }
}

在这个例子中,greet 函数具有两个具有默认值的参数。当在 Java 代码中使用这个类时,可以选择性地提供这些参数,就像在 Kotlin 中一样。Kotlin 编译器将生成以下 Java 重载:

void greet() {
    // ...
}

void greet(String name) {
    // ...
    }

void greet(String name, String exclamation) {
        // ...
    }

这样,在 Java 中使用这些函数时,就可以方便地省略带有默认值的参数。

@JvmMultifileClass

将多个 Kotlin 文件编译为同一个 Java 类。需要与 @file:JvmName 注解一起使用。文件 A.kt:

@file:JvmName("Utils")
@file:JvmMultifileClass

fun foo() {
    println("Function foo from A.kt")
}

文件 B.kt:

@file:JvmName("Utils")
@file:JvmMultifileClass

fun bar() {
    println("Function bar from B.kt")
}

在 Java 代码中,你可以使用 Utils.foo()Utils.bar() 来调用这两个函数。

@JvmMultifileClass

@JvmMultifileClass 是一个 Kotlin 注解,它允许你将多个 Kotlin 文件的内容合并到同一个 Java 类中。这对于生成的 Java 字节码有一定的优化作用,同时在调用这些函数时,也为 Java 代码提供了更简洁的 API。

要使用 @JvmMultifileClass 注解,请遵循以下步骤:

  1. 在你希望合并的所有 Kotlin 文件的顶部添加 @file:JvmName("YourClassName") 注解,其中 YourClassName 是你希望生成的 Java 类的名称。注意 @file: 前缀,它表示该注解应用于整个文件。
  2. 在所有这些 Kotlin 文件的顶部添加 @file:JvmMultifileClass 注解。

这样,编译器会将这些文件中的所有顶级函数和属性合并到一个名为 YourClassName 的 Java 类中。

以下是一个使用 @JvmMultifileClass 注解的示例:

File1.kt:

@file:JvmName("MyUtils")
@file:JvmMultifileClass

fun printHello() {
    println("Hello")
}

File2.kt:

@file:JvmName("MyUtils")
@file:JvmMultifileClass

fun printGoodbye() {
    println("Goodbye")
}

在这个例子中,File1.ktFile2.kt 中的顶级函数将被合并到一个名为 MyUtils 的 Java 类中。在 Java 代码中调用这些函数时,可以使用如下语法:

MyUtils.printHello();
MyUtils.printGoodbye();

注意,如果没有 @JvmMultifileClass 注解,这些函数将分别位于 File1KtFile2Kt 类中。

@Strictfp

用于表示类或方法应该遵循严格的浮点运算规则。在严格浮点运算模式下,所有的浮点计算将遵循 IEEE 754 标准,确保浮点运算的结果在所有平台上是一致的。这有助于提高跨平台的可移植性,因为浮点数的计算结果可能因处理器硬件或操作系统的不同而有所差异。

然而,在 Kotlin 中, @Strictfp 注解不可用。Kotlin 目前不支持严格浮点运算模式。尽管 Kotlin 可以调用 Java 代码,但你不能直接在 Kotlin 代码中使用 @Strictfp

如果你需要在 Kotlin 项目中使用严格浮点运算,可以考虑将与浮点运算相关的代码实现为 Java 类,并在该类上使用 @Strictfp 注解。然后,你可以在 Kotlin 代码中调用这个 Java 类中的方法。这样,你可以确保这些方法遵循严格的浮点运算规则。

@JvmName

  1. 用于指定 Kotlin 函数在 Java 代码中的名称。这在需要解决 Kotlin 函数与 Java 方法之间的命名冲突时非常有用。
@JvmName("listSize")
fun List<*>.size() = this.size

在 Java 代码中,你可以使用 listSize(list) 而不是 list.size() 来调用这个函数。

@JvmStatic

将 Kotlin 对象的成员函数或伴生对象的成员函数暴露为 Java 静态方法。

object MySingleton {
    @JvmStatic
    fun foo() {
        println("Called from Java as a static method")
    }
}

在 Java 代码中,你可以使用 MySingleton.foo() 调用这个方法。

@JvmField

将 Kotlin 属性暴露为 Java 字段,而不是通过 getter 和 setter 访问。

class MyClass {
    @JvmField
    val myField = "Hello, World!"
}

在 Java 代码中,你可以使用 myClassInstance.myField 直接访问这个字段。

@JvmOverloads

为 Kotlin 函数生成 Java 重载方法,使 Java 代码能够使用 Kotlin 函数的默认参数。

@JvmOverloads
fun greet(name: String = "World") {
    println("Hello, $name!")
}

在 Java 代码中,你可以使用 greet()greet("Alice") 调用这个函数。

@JvmWildcard

指示 Kotlin 泛型类型参数在 Java 代码中应被视为通配符类型。

fun printList(list: List<@JvmWildcard String>) {
    list.forEach { println(it) }
}

在 Java 代码中,你可以将 List<?> 类型的参数传递给这个函数。

@JvmDefault

用于指示 Kotlin 接口的默认方法应在 Java 代码中可用。这需要在 Kotlin 编译器中启用 JVM 默认方法支持。

@JvmDefault
interface MyInterface {
    fun printHello() {
        println("Hello, World!")
    }
}

在 Java 代码中,你可以创建一个实现 MyInterface 的类,并在其中直接使用 printHello() 方法。

@JvmSuppressWildcards

指示 Kotlin 泛型类型参数在 Java 代码中不应被视为通配符类型。

fun printList(list: List<@JvmSuppressWildcards String>) {
    list.forEach { println(it) }
}

在 Java 代码中,你不能将 List<?> 类型的参数传递给这个函数,而必须使用 List 类型的参数。

@JvmInline

标记一个内联类,使 Kotlin 编译器在编译时内联类的实例。这有助于减少运行时开销。

@JvmInline
value class UserId(val id: Int)

在 Kotlin 和 Java 代码中,UserId 类型的值在编译时会被替换为其包装的 Int 类型值。

@JvmWildcard

@JvmWildcard 是一个 Kotlin 注解,主要用于 Java 互操作场景。在 Kotlin 中,泛型类型参数默认具有不变性(invariance),这与 Java 的通配符类型不同。 @JvmWildcard 注解用于告诉 Kotlin 编译器在生成 Java 字节码时,将指定的泛型类型参数转换为通配符类型参数。

这个注解通常在 Kotlin 类型参数声明为 out 的情况下使用,以确保在 Java 中它具有正确的通配符上界。在 Kotlin 中,你不能直接将 @JvmWildcard 应用于类型参数,而是需要将其应用于类型投影。

这里有一个简单的示例:

class KotlinClass {
    fun exampleFunction(list: MutableList<@JvmWildcard String>) {
        // ...
    }
}

在这个示例中,exampleFunction 接受一个 MutableList 类型的参数,其中的泛型类型参数为 String。使用 @JvmWildcard 注解后,当从 Java 代码中调用这个函数时,参数类型将被解释为 MutableList<?> 。这样可以确保在 Java 代码中,你可以使用 MutableList 的任何子类型传递给该函数。

请注意, @JvmWildcard 注解只能用于类型投影,而不能直接应用于类型参数。在上面的示例中,它应用于 MutableList 的类型参数。

@JvmSynthetic

@JvmSynthetic 是一个 Kotlin 注解,用于标记生成的 JVM 成员(方法、字段等),使其在 Java 源代码中不可见。这意味着带有 @JvmSynthetic 注解的成员可以在 Kotlin 代码中使用,但不能在 Java 代码中使用。这有助于实现 Kotlin 和 Java 之间的 API 隔离,并隐藏 Kotlin 生成的一些内部实现细节。

@JvmSynthetic 注解通常用于以下情况:

  1. 隐藏 Kotlin 编译器生成的成员,这些成员在 Java 中可能会引起混淆或不希望暴露给 Java 代码。
  2. 当需要提供 Kotlin 专用的 API 时,可以使用 @JvmSynthetic 隐藏这些成员,以防止 Java 代码错误地使用它们。

下面是一个使用 @JvmSynthetic 的示例:

class Example {
    @JvmSynthetic
    fun kotlinOnlyFunction() {
        // ...
    }
}

在这个示例中,kotlinOnlyFunction 方法被标记为 @JvmSynthetic。这意味着它可以在 Kotlin 代码中使用,但在 Java 代码中是不可见的。

请注意, @JvmSynthetic 注解不会影响 Kotlin 之间的可见性。另外,虽然 @JvmSynthetic 成员在 Java 代码中不可见,但它们仍然存在于生成的字节码中,并且可以通过反射访问。

@JvmSuppressWildcards

@JvmSuppressWildcards 是一个 Kotlin 注解,用于禁止 Kotlin 编译器在生成的 Java 字节码中为类型参数插入通配符。这对于在 Kotlin 与 Java 之间的互操作性以及使用泛型时非常有用。

在 Kotlin 中,泛型参数默认具有声明处型变(declaration-site variance),而在 Java 中,默认情况下没有声明处型变。为了在两种语言之间实现更好的互操作性,Kotlin 编译器会在生成的 Java 字节码中使用通配符来表示类型参数的型变。

但是,在某些情况下,我们可能不希望这样做。这就是 @JvmSuppressWildcards 注解派上用场的地方。通过在类型参数前添加 @JvmSuppressWildcards 注解,可以告诉 Kotlin 编译器不要为该类型参数生成通配符。

以下是一个使用 @JvmSuppressWildcards 注解的示例:

fun exampleFunction(list: List<@JvmSuppressWildcards String>) {
    // ...
}

在这个例子中, @JvmSuppressWildcards 注解用于类型参数 String。这意味着在生成的 Java 字节码中,该类型参数不会被表示为通配符。这可以避免某些 Java 互操作性问题,特别是在使用泛型时。