跳至主要内容

註解 (Annotations)

註解(Annotations)是用於將元數據附加到程式碼的一種方式。要宣告一個註解,請在類別(class)前面加上 annotation 修飾符:

annotation class Fancy

可以通過使用元註解(meta-annotations)來註解註解類別,以此指定註解的附加屬性:

  • @Target 指定可以使用該註解來註解的元素種類(例如類別、函數、屬性和表達式);
  • @Retention 指定註解是否儲存在已編譯的類別檔案中,以及它在運行時是否可通過反射(reflection)可見(預設情況下,兩者都為 true);
  • @Repeatable 允許在單個元素上多次使用同一個註解;
  • @MustBeDocumented 指定該註解是公共 API 的一部分,並且應包含在產生的 API 文件中顯示的類別或方法簽名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

用法(Usage)

@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}

如果您需要註解類別的主建構式(primary constructor),您需要將 constructor 關鍵字新增到建構式宣告中,並在其前面新增註解:

class Foo @Inject constructor(dependency: MyDependency) { ... }

您還可以註解屬性(property)的取值器(accessors):

class Foo {
var x: MyDependency? = null
@Inject set
}

建構式(Constructors)

註解可以具有帶參數的建構式。

annotation class Special(val why: String)

@Special("example") class Foo {}

允許的參數類型為:

  • 對應於 Java 原始類型(primitive types)的類型(Int、Long 等)
  • 字串(Strings)
  • 類別(Classes)(Foo::class
  • 列舉(Enums)
  • 其他註解
  • 上述類型的陣列(Arrays)

註解參數不能具有可為空的類型(nullable types),因為 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<out Any>)

@Ann(String::class, Int::class) class MyClass

實例化(Instantiation)

在 Java 中,註解類型是一種介面(interface)的形式,因此您可以實作它並使用一個實例(instance)。作為這種機制的替代方案,Kotlin 允許您在任意程式碼中呼叫註解類別的建構式,並以類似的方式使用產生的實例。

annotation class InfoMarker(val info: String)

fun processInfo(marker: InfoMarker): Unit = TODO()

fun main(args: Array<String>) {
if (args.isNotEmpty())
processInfo(getAnnotationReflective(args))
else
processInfo(InfoMarker("default"))
}

此 KEEP 中了解有關註解類別實例化的更多資訊。

Lambda 表達式(Lambdas)

註解也可以在 Lambda 表達式上使用。它們將被應用於生成 Lambda 表達式主體的 invoke() 方法中。 這對於像 Quasar 這樣的框架很有用,它使用註解進行並發控制(concurrency control)。

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) }

註解的使用位置目標(Annotation use-site targets)

當您註解屬性或主建構式參數時,會從相應的 Kotlin 元素生成多個 Java 元素,因此在生成的 Java 字節碼(bytecode)中,註解存在多個可能的位置。 要指定應如何準確地生成註解,請使用以下語法:

class Example(@field:Ann val foo,    // 註解 Java 欄位(field)
@get:Ann val bar, // 註解 Java getter
@param:Ann val quux) // 註解 Java 建構式參數

相同的語法可用於註解整個檔案。 為此,請將帶有目標 file 的註解放在檔案的頂層,放在套件(package)指令之前,或者如果該檔案位於預設套件中,則放在所有引入(import)之前:

@file:JvmName("Foo")

package org.jetbrains.demo

如果您有多個具有相同目標的註解,則可以通過在目標後新增方括號(brackets)並將所有註解放在方括號內來避免重複目標:

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 annotations)

Java 註解與 Kotlin 100% 相容:

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

陣列作為註解參數(Arrays as annotation parameters)

如果 Java 中的 value 參數具有陣列類型,則它在 Kotlin 中變為 vararg 參數:

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

對於具有陣列類型的其他參數,您需要使用陣列字面值語法(array literal syntax)或 arrayOf(...)

// Java
public @interface AnnWithArrayMethod {
String[] names();
}
@AnnWithArrayMethod(names = ["abc", "foo", "bar"]) 
class C

存取註解實例的屬性(Accessing properties of an annotation instance)

註解實例的值作為屬性暴露給 Kotlin 程式碼:

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

不生成 JVM 1.8+ 註解目標的能力(Ability to not generate JVM 1.8+ annotation targets)

如果 Kotlin 註解在其 Kotlin 目標中具有 TYPE,則該註解會對應到其 Java 註解目標列表中的 java.lang.annotation.ElementType.TYPE_USE。 這就像 TYPE_PARAMETER Kotlin 目標對應到 java.lang.annotation.ElementType.TYPE_PARAMETER Java 目標一樣。 對於 API 等級低於 26 的 Android 客戶端(Android clients)而言,這是一個問題,因為它們的 API 中沒有這些目標。

要避免生成 TYPE_USETYPE_PARAMETER 註解目標,請使用新的編譯器引數(compiler argument)-Xno-new-java-annotation-targets

可重複的註解(Repeatable annotations)

就像 在 Java 中 一樣,Kotlin 具有可重複的註解,可以將其多次應用於單個程式碼元素。 要使您的註解可重複,請使用 @kotlin.annotation.Repeatable 元註解標記其宣告。 這將使其在 Kotlin 和 Java 中都可重複。 Kotlin 端也支援 Java 可重複的註解。

與 Java 中使用的方案的主要區別在於缺少 包含註解containing annotation),Kotlin 編譯器會使用預定義的名稱自動生成該註解。 對於下面範例中的註解,它將生成包含註解 @Tag.Container

@Repeatable
annotation class Tag(val name: String)

// 編譯器生成 @Tag.Container 包含註解

您可以通過應用 @kotlin.jvm.JvmRepeatable 元註解並傳遞顯式宣告的包含註解類別作為引數,從而為包含註解設定自訂名稱:

@JvmRepeatable(Tags::class)
annotation class Tag(val name: String)

annotation class Tags(val value: Array<Tag>)

要通過反射提取 Kotlin 或 Java 可重複的註解,請使用 KAnnotatedElement.findAnnotations() 函數。

此 KEEP 中了解有關 Kotlin 可重複註解的更多資訊。