亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Kotlin 的注解

從這篇文章我們一起來看下 Kotlin 中的注解。Kotlin 中的注解是 100% 與 Java 注解兼容的,有很多相同的地方,但是也有一些不同的地方。一起來瞅瞅吧~

1. 注解的本質

注解實際上就是一種代碼標簽,它作用的對象是代碼。它可以給特定的注解代碼標注一些額外的信息。然而這些信息可以選擇不同保留時期,比如源碼期、編譯期、運行期。然后在不同時期,可以通過某種方式獲取標簽的信息來處理實際的代碼邏輯,這種方式常常就是我們所說的反射。

2. 注解的定義

在 Kotlin 中注解核心概念和 Java 一樣,注解就是為了給代碼提供元數據。并且注解是不直接影響代碼的執行。一個注解允許你把額外的元數據關聯到一個聲明上,然后元數據就可以被某種方式(比如運行時反射方式以及一些源代碼工具)訪問。

3. 注解的聲明

在 Kotlin 中的聲明注解的方式和 Java 稍微不一樣,在 Java 中主要是通過 @interface關鍵字來聲明,而在Kotlin 中只需要通過 annotation class 來聲明, 需要注意的是在 Kotlin 中編譯器禁止為注解類指定類主體,因為在 Kotlin 中注解只是用來定義關聯的聲明和表達式的元數據的結構

  • Kotlin 注解聲明
package com.mikyou.annotation
//和一般的聲明很類似,只是在class前面加上了annotation修飾符
annotation class TestAnnotation(val value: String)
  • Java 注解聲明
package com.mikyou.annotation;
//java中的注解通過@interface關鍵字進行定義,它和接口聲明類似,只不過在前面多加@
public @interface TestAnnotation {
    String value();
}

4. 注解的應用

在上一步我們知道了如何聲明和定義標簽了,那么接下來就是用這個標簽,如何把我們定義好的標簽貼到指定的代碼上。在 Kotlin 中使用注解和 Java 一樣。要應用一個注解都是 @注解類名。

@Target(AnnotationTarget.FUNCTION)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class TestAnnotation(val value: Int)//和一般的聲明很類似,只是在class前面加上了annotation修飾符

class Test {
    @TestAnnotation(value = 1000)
    fun test() {//給test函數貼上TestAnnotation標簽(添加TestAnnotation注解)
        //...
    }
}

在很多常見的 Java 或 Kotlin 框架中大量使用了注解,比如我們最常見的 JUnit 單元測試框架:

class ExampleUnitTest {
    @Test //@Test注解就是為了告訴JUnit框架,這是一個測試方法,當做測試調用。
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

Kotlin 中注解類中還可以擁有注解類作為參數,不妨來看下 Kotlin 中對 @Deprecated這個注解源碼定義,以及它的使用。@Deprecated 注解在原來的 Java 基礎增強了一個 ReplaceWith 功能.??梢灾苯釉谑褂昧死系?API 時,編譯器可以根據 ReplaceWith 中的新 API,自動替換成新的 API。這一點在 Java 中是做不到的,你只能點擊進入這個 API 查看源碼來正確使用新的 API。

//@Deprecated注解比Java多了ReplaceWith功能, 這樣當你在調用remove方法,編譯器會報錯。使用代碼提示會自動IntelliJ IDEA不僅會提示使用哪個函數提示替代它,而且會快速自動修正。
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"), level = DeprecationLevel.ERROR)//定義的級別是ERROR級別的,這樣當你在調用remove方法,編譯器會報錯。
@kotlin.internal.InlineOnly
public inline fun <T> MutableList<T>.remove(index: Int): T = removeAt(index)

@Deprecated 注解的 remove 函數使用:

//Deprecated注解的使用
fun main(args: Array<String>) {
    val list = mutableListOf("a", "b", "c", "d", "e")
    list.remove(3)//這里會報錯, 通過remove函數注解定義,這個remove函數在定義的level是ERROR級別的,所以編譯器直接拋錯
}

圖片描述

圖片描述

最后來看下 @Deprecated 注解的定義:

@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith(""),//注解類中構造器可以使用注解類作為函數參數
        val level: DeprecationLevel = DeprecationLevel.WARNING
)
@Target()
@Retention(BINARY)
@MustBeDocumented
public annotation class ReplaceWith(val expression: String, vararg val imports: String)

注意:注解類中只能擁有如下類型的參數: 基本數據類型、字符串、枚舉、類引用類型、其他的注解類(例如Deprecated注解類中的ReplaceWith注解類)

5. Kotlin 中的元注解

和 Java 一樣在 Kotlin 中,一個 Kotlin 注解類自己本身也可以被注解,可以給注解類加注解。我們把這種注解稱為元注解,可以把它理解為一種基本的注解,也可以把它理解為一種特殊的標簽,用于標注標簽的標簽。

Kotlin 中的元注解類定義于 kotlin.annotation 包中,主要有: @Target、@Retention@Repeatable、@MustBeDocumented 4 種元注解, 相比 Java 中 5 種元注解: @Target、@Retention@Repeatable、@Documented、**@Inherited **少了 @Inherited元注解。

5.1 @Target 元注解

介紹

Target 顧名思義就是目標對象,也就是這個標簽作用于哪些代碼中目標對象,可以同時指定多個作用的目標對象。

源碼定義

@Target(AnnotationTarget.ANNOTATION_CLASS)//可以給標簽自己貼標簽
@MustBeDocumented
//注解類構造器參數是個vararg不定參數修飾符,所以可以同時指定多個作用的目標對象
public annotation class Target(vararg val allowedTargets: AnnotationTarget)

@Target 元注解作用的目標對象

在@Target注解中可以同時指定一個或多個目標對象,那么到底有哪些目標對象呢?這就引出另外一個AnnotationTarget 枚舉類:

public enum class AnnotationTarget {
    CLASS, //表示作用對象有類、接口、object對象表達式、注解類
    ANNOTATION_CLASS,//表示作用對象只有注解類
    TYPE_PARAMETER,//表示作用對象是泛型類型參數(暫時還不支持)
    PROPERTY,//表示作用對象是屬性
    FIELD,//表示作用對象是字段,包括屬性的幕后字段
    LOCAL_VARIABLE,//表示作用對象是局部變量
    VALUE_PARAMETER,//表示作用對象是函數或構造函數的參數
    CONSTRUCTOR,//表示作用對象是構造函數,主構造函數或次構造函數
    FUNCTION,//表示作用對象是函數,不包括構造函數
    PROPERTY_GETTER,//表示作用對象是屬性的getter函數
    PROPERTY_SETTER,//表示作用對象是屬性的setter函數
    TYPE,//表示作用對象是一個類型,比如類、接口、枚舉
    EXPRESSION,//表示作用對象是一個表達式
    FILE,//表示作用對象是一個File
    @SinceKotlin("1.1")
    TYPEALIAS//表示作用對象是一個類型別名
}

5.2 @Retention 元注解

介紹

Retention 對應的英文意思是保留期,當它應用于一個注解上表示該注解保留存活時間,不管是Java還是Kotlin 一般都有三種時期: 源代碼時期(SOURCE)編譯時期(BINARY)、運行時期(RUNTIME)。

源碼定義

@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象是注解類
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)//接收一個參數,該參數有個默認值,默認是保留在運行時期

@Retention 元注解的取值

@Retention 元注解取值主要來源于 AnnotationRetention 枚舉類

public enum class AnnotationRetention {
    SOURCE,//源代碼時期(SOURCE): 注解不會存儲在輸出class字節碼中
    BINARY,//編譯時期(BINARY): 注解會存儲出class字節碼中,但是對反射不可見
    RUNTIME//運行時期(RUNTIME): 注解會存儲出class字節碼中,也會對反射可見, 默認是RUNTIME
}

5.3 @MustBeDocumented元注解

介紹

該注解比較簡單主要是為了標注一個注解類作為公共API的一部分,并且可以保證該注解在生成的API文檔中存在。

源碼定義

@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是注解類
public annotation class MustBeDocumented

5.4 @Repeatable元注解

介紹

這個注解決定標注的注解在一個注解在一個代碼元素上可以應用兩次或兩次以上。

源碼定義

@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是注解類
public annotation class Repeatable

5.5 為啥Kotlin去掉了Java中的@Inherited元注解

Java 中的 @Inherited 元注解介紹

Inheried 顧名思義就是繼承的意思,但是這里需要注意并不是表示注解類可以繼承,而是如果一個父類被貼上 @Inherited 元注解標簽,那么它的子類沒有任何注解標簽的話,這個子類就會繼承來自父類的注解。類似下面的例子:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

@TestAnnotation
class Animal {
    //...
}

class Cat extends Animal{//也會擁有來自父類Animal的@TestAnnotation注解
    //...
}

Kotlin 為啥不需要 @Inherited 元注解?

關于這個問題實際上在 Kotlin 官網的 discuss 中就有人提出了這個問題,具體感興趣的可以去看看:Inherited annotations and other reflections enchancements。

這里大概說下原因,我們都知道在 Java 中,無法找到子類方法是否重寫了父類的方法。因此不能繼承父類方法的注解。然而 Kotlin 目前不需要支持這個 @Inherited 元注解,因為 Kotlin 可以做到,如果反射提供了override 標記而且很容易做到。

6. 注解的使用場景

  • 提供信息給編譯器: 編譯器可以利用注解來處理一些,比如一些警告信息,錯誤等;
  • 編譯階段時處理: 利用注解信息來生成一些代碼,在Kotlin生成代碼非常常見,一些內置的注解為了與Java API的互操作性,往往借助注解在編譯階段生成一些額外的代碼;
  • 運行時處理: 某些注解可以在程序運行時,通過反射機制獲取注解信息來處理一些程序邏輯。