注解
注解声明
注解是给代码附加元数据的手段。如果要定义一个注解,只需在类前面放置一个 annotation
修饰符:
annotation class Fancy
使用元注解(meta-annotation)可以给注解指定额外属性:
@Target
用于指定可作用其上的元素(类、函数、属性和表达式等)@Retention
用于指定注解要存活到 class 还是运行时@Repeatable
允许同样的注解在一个元素上使用多次@MustBeDocumented
用于指定注解是 public API 的一部分,API 文档的类或者方法签名需要包含这个注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
用法
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
如果要注解首要构造器,在构造器的声明中需要添加 constructor
关键字,并且把注解加在它前面:
class Foo @Inject constructor(dependency: MyDependency) {
// ...
}
也可以注解属性访问器:
class Foo {
var x: MyDependency? = null
@Inject set
}
构造器
注解可以有带参数的构造器。
annotation class Special(val why: String)
@Special("example") class Foo {}
可用的类型参数有:
- 对应于 Java 基础类型(Int, Long 等)的类型
- 字符串
- 类(
Foo::class
) - 枚举
- 其他标记
- 以上类型的数组
注解的参数不可以是空类型,因为 JVM 不支持把 null
作为注解属性的值来存储。
如果一个注解用作其他注解的参数,名字前面没有 @
符号:
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String
val replaceWith: ReplaceWidth = ReplaceWith(""))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this == other"))
如果要把类作为注解的实参,可以使用 Kotlin 类(KClass
)。Kotlin 编译器会自动把它转化成 Java 类,这样 Java 代码就能够正常识别出注解和实参。
import kotlin.reflect.KClass
annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)
@Ann(String::class, Int::class) class MyClass
lambda
注解也可以用于 lambda。它们会应用到的 invoke()
方法(lambda 体会生成于内)。对于 Quasar 这样的框架会比较有用,因为它把注解用作并发控制器。
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
注解使用处目标(Use-site Target)
当注解一个属性或者首要构造器的参数时,对应的一个 Kotlin 元素会生成多个 Java 元素,因此注解在 Java 字节码中会有多个可能的位置。为了明确指明注解的位置,可以使用如下语法:
class Example(@field:Ann val foo, // annotate Java field
@get:Ann val bar, // annotate Java getter
@param:Ann val quux) // annotate Java constructor parameter
同样的语法可用于注解整个文件。在文件顶部,package 指令之前(如果是文件是默认 package,则在 import 之前)放置一个目标是 file
的标记:
@file:JvmName("Foo")
package org.jetbrains.demo
如果一个目标(target)有多个注解,可以把所有的注解放进作用目标之后的方括号内,这样可以避免重复:
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
使用处目标的完整清单:
file
property
(目标是它的注解对 Java 不可见);field
get
(属性 getter)set
(属性 setter)receiver
(扩展函数或扩展属性的接收者参数);param
(构造器参数);setparam
(属性 setter 参数);delegate
(存储代理属性的代理实例的字段)
如果要注解扩展函数的接收者参数,可使用如下语法:
fun @receiver:Fancy String.myExtension() { }
如果不指明使用处目标,那么选定的目标的就会变成使用注解的 @Target
注解。如果有多个可用目标,下面列表中出现的第一个可用目标会被使用:
param
property
field
Java 注解
Java 的注解和 Kotlin 100% 兼容:
import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*
class Tests {
// apply @Rule annotation to property getter
@get:Rule val tempFolder = TemporaryFolder()
@Test fun simple() {
val f = tempFolder.newFile()
assertEquals(42, getTheAnswer())
}
}
因为 Java 没有定义注解参数的顺序,所以不能使用常规的函数调用语法来传递参数。不过可以利用命名参数的语法:
// Java
public @interface Ann {
int intValue();
String stringValue();
}
// Kotlin
@Ann(intValue = 1, stringValue = "abc") class C
跟 Java 一样,有一个例外,value
参数的值无需指定参数名:
// Java
public @interface AnnWithValue {
String value()
}
// Kotlin
@AnnWithValue("abc") class C
数组作为注解参数
在 Java 中,如果 value
的实参是一个数组类型,Kotlin 会将其作为 vararg
来处理:
// Java
public @interface AnnWithArrayValue {
String[] value();
}
// Kotlin
@AnnWithArrayValue("abc", "foo", "bar") class C
对于其他数组类型的参数,需要使用数组字面量预发(Kotlin 1.2 之后开始支持)或者使用 arrayOf(...)
:
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
@AnnWithArrayMethod(names = ["abc", "foo", "bar"])
class C
// Older Kotlin versions:
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar"))
class D
访问注解实例的属性
注解实例的值会暴露为 Kotlin 的属性:
// Java
public @interface Ann {
int value();
}
fun foo(ann: Ann) {
val i = ann.value
}