注解是将元数据附加到代码的手段,要声明一个注解,只要把annnotation关键字放在class前面进行修饰即可.
annotation class Fancy
注解的附加属性可以通过注解类和元注解来指定:
- @Target 指定可以用注解进行注解的元素类型
- @Retention 指定注解是否存储在编译后的类文件中,以及是否在运行时通过反射可见(默认情况下,这两个都是真的)
- @Repeatable 允许在同一个元素上多次使用相同的注解
- @MustBeDocumented 指定注解是公共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: ReplaceWith = 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)
@Ann(String::class, Int::class) class MyClass
注解也可以应用在lambdas上面,当lambdas主体代码生成的时候,注解就会应用在invoke方法中,这对于并发控制的框架(Quasar)是非常有用的
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
当需要把注解应用在属性或主要构造方法的参数时,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
==@field,@get,@param都是Java中的注解==
相同的语法也可以应用在整个文件上,为了达到这个目的,你还需要在文件结构最顶层加上目标文件注解file,在包名和所有import语句之前:
@file JvmName("Foo")
package com.mk;
如果在一个目标中有多个注解,为了避免重复添加目标,可以在目标后面添加括号,把所有注解都放在括号里面:
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中的注解在Kotlin中也可以编译通过:
import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*
class Tests {
// 应用 @Rule 注解在属性的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
就算是其他类型的数组,也可以用arrayOf来明确指定:
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
// Kotlin
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar")) class C
注解实例的值暴露给Kotlin的时候就是一个属性:
// Java
public @interface Ann {
int value();
}
// Kotlin
fun foo(ann: Ann) {
val i = ann.value
}