注解

应用注解

要应用一个注解,以@作为注解名的前缀,并放在要注解的声明的最前面。

import org.junit.*

class MyTest {
    @Test
    fun testTrue() {
        Assert.assertTrue(true)
    }
}
1
2
3
4
5
6
7
8

Kotlin允许对任意的表达式应用注解。

fun test(list: List<*>) {
    @Suppress("UNCHECKED_CAST")
    val strings = list as List<String>
}
1
2
3
4

注解实参

注解只能拥有如下类型的参数:基本数据类型、字符串、枚举、类引用、其他的注解类和前面这些类型的数组形式。

Kotlin指定注解实参的语法与Java有一些差别,主要体现在:

  • 要把一个类指定为注解实参,在类名后加上::class
@MyAnnotation(MyClass::class)
1
  • 要把另一个注解指定为一个实参,去掉注解名称前面的@。如,在@Deprecated中使用ReplaceWith注解来提供一个替代者,以支持平滑地过渡到API的新版本。
@Deprecated("Use removeAt(index) instead", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { ... }
1
2
  • 要把一个数组指定为实参,使用[]arrayof()
@RequestMapping(path = ["/foo", "/bar"])
1

注解实参需要在编译期就是已知的。如,用const修饰符标记一个属性,来告知编译器这是一个编译器常量,可将它用作注解实参。

const val TEST_TIMEOUT = 1000L

@Test(timeout = TEST_TIMEOUT)
fun testMethod() { ... }
1
2
3
4

注解目标

Kotlin代码中的单个声明往往会对应多个Java声明,而且每个Java声明都能携带注解,即一个Kotlin声明可能携带多个注解。

使用点目标声明被用来说明要注解的元素。点目标位于@和注解名之间,并用冒号和注解名隔开。如,点目标get会使@Rule注解被应用到属性的getter上。

@get:Rule
var email: String? = null
1
2

Kotlin支持的点目标如下:

  • property —— Java注解不能应用这种点目标。

  • field —— 属性生成的字段。

  • get —— 属性的getter。

  • set —— 属性的setter。

  • receiver —— 扩展函数或扩展属性的接收者参数。

  • param —— 构造方法的参数。

  • setparam —— 属性的setter的参数。

  • delegate —— 委托属性存储委托实例的字段。

  • file —— 包含在文件中声明的顶层函数和属性的类。任何应用到file目标的注解都必须放在文件的顶层,放在package指令之前。

用注解控制Java API

Kotlin提供了各种注解来控制Kotlin编写的声明如何编译成字节码并暴露给Java调用者,主要分为以下两种:

  • 代替Java语言中对应的关键字。比如,@Volatile代替volatile关键字;@Strictfp代替strictfp关键字。

  • 用来改变Kotlin声明对Java调用者的可见性。如@JvmName@JvmStatic@JvmOverloads@JvmField

@JvmName会改变由Kotlin生成的Java方法或字段的名称。

@JvmStatic被用在对象声明或伴生对象的方法上,把它们暴露成Java的静态方法。

@JvmOverloads指导Kotlin编译器为带默认参数值的函数生成多个重载函数。

@JvmField可应用于一个属性,把这个属性暴露成一个没有访问器的公有Java字段。

声明注解

利用annotation class声明一个注解类。

annotation class JsonExclude
1

编译器禁止为一个注解类指定类主体。对拥有参数的注解来说,要在类的主构造方法中声明这些参数。

annotation class JsonName(val name: String)
1

可以使用命名实参语法让注解实参的名称变成显式。

@JsonName(name = "Xxx")
1

因为name是JsonName构造方法的第一个形参,它的名称可以省略。

@JsonName("Xxx")
1

Java注解中往往有一个叫做value的方法,当为它指定注解实参时可省略名称,而对value以外的方法需要提供显式名称的实参。

public @interface JsonName {
    String value();
}
1
2
3
@JsonName("Xxx")
1

使用类做注解参数

KClass是Java的java.lang.Class类型在Kotlin中的对应类型,用来保存Kotlin类的引用。

annotation class DeserializeInterface(val targetClass: KClass<out Any>)
1

使用类名称后跟上::class关键字来引用一个类。

@DeserializeInterface(Company::class)
1

元注解

可以应用到注解类上的注解被称作元注解

Kotlin标准库的kotlin.annotation.Annotations.kt定义了一些常用的元注解。

@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented
1
2
3
4
5
6
7
8
9
10
11
12

@Target

可使用@Target来指定某个注解可以被应用的元素类型。如果不使用它,这个注解默认可被应用到所有的声明上。

// JsonExclude注解只能应用在属性上
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude
1
2
3

AnnotationTarget枚举的值列出了可以应用注解的全部可能的目标,常用值如下:

  • CLASS —— 类、接口、对象、注解类。

  • ANNOTATION_CLASS —— 注解类。

  • PROPERTY —— 属性。

  • PROPERTY_GETTER —— 属性的getter。

  • PROPERTY_SETTER —— 属性的setter。

  • FIELD —— 字段。

  • CONSTRUCTOR —— 主构造函数或二级构造函数。

  • FUNCTION —— 函数,不包含构造函数。

  • EXPRESSION —— 任意的表达式。

Target注解可以接受一个或多个AnnotationTarget值。

注意,在Java代码中无法使用目标为PROPERTY的注解,除非添加第二个目标FIELD。

@Retention

使用@Retention来指定某个注解是否会被存储到.class文件,以及在运行时是否可以通过反射来访问它。

Java默认会在.class文件中保留注解,但不会让它们在运行时被访问到。而Kotlin默认会在.class文件中保留注解,而且会让它们在运行时能被访问到。

@Retention接收一个AnnotationRetention枚举值来设置它的行为。

public enum class AnnotationRetention {
    /** Annotation isn't stored in binary output */
    SOURCE,
    /** Annotation is stored in binary output, but invisible for reflection */
    BINARY,
    /** Annotation is stored in binary output and visible for reflection (default retention) */
    RUNTIME
}
1
2
3
4
5
6
7
8