注解

注解声明

注解是给代码附加元数据的手段。如果要定义一个注解,只需在类前面放置一个 annotation 修饰符:

  1. annotation class Fancy

使用元注解(meta-annotation)可以给注解指定额外属性:

  • @Target 用于指定可作用其上的元素(类、函数、属性和表达式等)
  • @Retention 用于指定注解要存活到 class 还是运行时
  • @Repeatable 允许同样的注解在一个元素上使用多次
  • @MustBeDocumented 用于指定注解是 public API 的一部分,API 文档的类或者方法签名需要包含这个注解
  1. @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
  2. AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
  3. @Retention(AnnotationRetention.SOURCE)
  4. @MustBeDocumented
  5. annotation class Fancy

用法

  1. @Fancy class Foo {
  2. @Fancy fun baz(@Fancy foo: Int): Int {
  3. return (@Fancy 1)
  4. }
  5. }

如果要注解首要构造器,在构造器的声明中需要添加 constructor 关键字,并且把注解加在它前面:

  1. class Foo @Inject constructor(dependency: MyDependency) {
  2. // ...
  3. }

也可以注解属性访问器:

  1. class Foo {
  2. var x: MyDependency? = null
  3. @Inject set
  4. }

构造器

注解可以有带参数的构造器。

  1. annotation class Special(val why: String)
  2. @Special("example") class Foo {}

可用的类型参数有:

  • 对应于 Java 基础类型(Int, Long 等)的类型
  • 字符串
  • 类(Foo::class
  • 枚举
  • 其他标记
  • 以上类型的数组

注解的参数不可以是空类型,因为 JVM 不支持把 null 作为注解属性的值来存储。

如果一个注解用作其他注解的参数,名字前面没有 @ 符号:

  1. annotation class ReplaceWith(val expression: String)
  2. annotation class Deprecated(
  3. val message: String
  4. val replaceWith: ReplaceWidth = ReplaceWith(""))
  5. @Deprecated("This function is deprecated, use === instead", ReplaceWith("this == other"))

如果要把类作为注解的实参,可以使用 Kotlin 类(KClass)。Kotlin 编译器会自动把它转化成 Java 类,这样 Java 代码就能够正常识别出注解和实参。

  1. import kotlin.reflect.KClass
  2. annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)
  3. @Ann(String::class, Int::class) class MyClass

lambda

注解也可以用于 lambda。它们会应用到的 invoke() 方法(lambda 体会生成于内)。对于 Quasar 这样的框架会比较有用,因为它把注解用作并发控制器。

  1. annotation class Suspendable
  2. val f = @Suspendable { Fiber.sleep(10) }

注解使用处目标(Use-site Target)

当注解一个属性或者首要构造器的参数时,对应的一个 Kotlin 元素会生成多个 Java 元素,因此注解在 Java 字节码中会有多个可能的位置。为了明确指明注解的位置,可以使用如下语法:

  1. class Example(@field:Ann val foo, // annotate Java field
  2. @get:Ann val bar, // annotate Java getter
  3. @param:Ann val quux) // annotate Java constructor parameter

同样的语法可用于注解整个文件。在文件顶部,package 指令之前(如果是文件是默认 package,则在 import 之前)放置一个目标是 file 的标记:

  1. @file:JvmName("Foo")
  2. package org.jetbrains.demo

如果一个目标(target)有多个注解,可以把所有的注解放进作用目标之后的方括号内,这样可以避免重复:

  1. class Example {
  2. @set:[Inject VisibleForTesting]
  3. var collaborator: Collaborator
  4. }

使用处目标的完整清单:

  • file
  • property (目标是它的注解对 Java 不可见);
  • field
  • get (属性 getter)
  • set (属性 setter)
  • receiver (扩展函数或扩展属性的接收者参数);
  • param (构造器参数);
  • setparam (属性 setter 参数);
  • delegate (存储代理属性的代理实例的字段)

如果要注解扩展函数的接收者参数,可使用如下语法:

  1. fun @receiver:Fancy String.myExtension() { }

如果不指明使用处目标,那么选定的目标的就会变成使用注解的 @Target 注解。如果有多个可用目标,下面列表中出现的第一个可用目标会被使用:

  • param
  • property
  • field

Java 注解

Java 的注解和 Kotlin 100% 兼容:

  1. import org.junit.Test
  2. import org.junit.Assert.*
  3. import org.junit.Rule
  4. import org.junit.rules.*
  5. class Tests {
  6. // apply @Rule annotation to property getter
  7. @get:Rule val tempFolder = TemporaryFolder()
  8. @Test fun simple() {
  9. val f = tempFolder.newFile()
  10. assertEquals(42, getTheAnswer())
  11. }
  12. }

因为 Java 没有定义注解参数的顺序,所以不能使用常规的函数调用语法来传递参数。不过可以利用命名参数的语法:

  1. // Java
  2. public @interface Ann {
  3. int intValue();
  4. String stringValue();
  5. }
  1. // Kotlin
  2. @Ann(intValue = 1, stringValue = "abc") class C

跟 Java 一样,有一个例外,value 参数的值无需指定参数名:

  1. // Java
  2. public @interface AnnWithValue {
  3. String value()
  4. }
  1. // Kotlin
  2. @AnnWithValue("abc") class C

数组作为注解参数

在 Java 中,如果 value 的实参是一个数组类型,Kotlin 会将其作为 vararg 来处理:

  1. // Java
  2. public @interface AnnWithArrayValue {
  3. String[] value();
  4. }
  1. // Kotlin
  2. @AnnWithArrayValue("abc", "foo", "bar") class C

对于其他数组类型的参数,需要使用数组字面量预发(Kotlin 1.2 之后开始支持)或者使用 arrayOf(...)

  1. // Java
  2. public @interface AnnWithArrayMethod {
  3. String[] names();
  4. }
  1. @AnnWithArrayMethod(names = ["abc", "foo", "bar"])
  2. class C
  3. // Older Kotlin versions:
  4. @AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar"))
  5. class D

访问注解实例的属性

注解实例的值会暴露为 Kotlin 的属性:

  1. // Java
  2. public @interface Ann {
  3. int value();
  4. }
  1. fun foo(ann: Ann) {
  2. val i = ann.value
  3. }