注解
注解声明
注解是给代码附加元数据的手段。如果要定义一个注解,只需在类前面放置一个 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)@MustBeDocumentedannotation 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: Stringval replaceWith: ReplaceWidth = ReplaceWith(""))@Deprecated("This function is deprecated, use === instead", ReplaceWith("this == other"))
如果要把类作为注解的实参,可以使用 Kotlin 类(KClass)。Kotlin 编译器会自动把它转化成 Java 类,这样 Java 代码就能够正常识别出注解和实参。
import kotlin.reflect.KClassannotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)@Ann(String::class, Int::class) class MyClass
lambda
注解也可以用于 lambda。它们会应用到的 invoke() 方法(lambda 体会生成于内)。对于 Quasar 这样的框架会比较有用,因为它把注解用作并发控制器。
annotation class Suspendableval 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}
使用处目标的完整清单:
fileproperty(目标是它的注解对 Java 不可见);fieldget(属性 getter)set(属性 setter)receiver(扩展函数或扩展属性的接收者参数);param(构造器参数);setparam(属性 setter 参数);delegate(存储代理属性的代理实例的字段)
如果要注解扩展函数的接收者参数,可使用如下语法:
fun @receiver:Fancy String.myExtension() { }
如果不指明使用处目标,那么选定的目标的就会变成使用注解的 @Target 注解。如果有多个可用目标,下面列表中出现的第一个可用目标会被使用:
parampropertyfield
Java 注解
Java 的注解和 Kotlin 100% 兼容:
import org.junit.Testimport org.junit.Assert.*import org.junit.Ruleimport 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 没有定义注解参数的顺序,所以不能使用常规的函数调用语法来传递参数。不过可以利用命名参数的语法:
// Javapublic @interface Ann {int intValue();String stringValue();}
// Kotlin@Ann(intValue = 1, stringValue = "abc") class C
跟 Java 一样,有一个例外,value 参数的值无需指定参数名:
// Javapublic @interface AnnWithValue {String value()}
// Kotlin@AnnWithValue("abc") class C
数组作为注解参数
在 Java 中,如果 value 的实参是一个数组类型,Kotlin 会将其作为 vararg 来处理:
// Javapublic @interface AnnWithArrayValue {String[] value();}
// Kotlin@AnnWithArrayValue("abc", "foo", "bar") class C
对于其他数组类型的参数,需要使用数组字面量预发(Kotlin 1.2 之后开始支持)或者使用 arrayOf(...):
// Javapublic @interface AnnWithArrayMethod {String[] names();}
@AnnWithArrayMethod(names = ["abc", "foo", "bar"])class C// Older Kotlin versions:@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar"))class D
访问注解实例的属性
注解实例的值会暴露为 Kotlin 的属性:
// Javapublic @interface Ann {int value();}
fun foo(ann: Ann) {val i = ann.value}
