注解

定義注解

Kotlin使用 annotation class 關(guān)鍵字(就像使用 enum class 定義枚舉類(lèi)一樣),定義注解非常簡(jiǎn)單赴精,Kottin 甚至不允許為注解定義注解體,也就是說(shuō)绞幌,注解后面不能有花括號(hào)蕾哟。

//定義一個(gè)簡(jiǎn)單的注解
annotation class MyClass

定義了該注解之后,就可以在程序的任何地方使用該注解莲蜘。使用注解的語(yǔ)法非常類(lèi)似于使 用 public谭确、 final 這樣的修飾符,通称鼻可用于修飾程序中的類(lèi)逐哈、方法、屬性问顷、接口等定義昂秃。通常會(huì)把注解放在所有修飾符之前禀梳。

//使用自Test 修飾類(lèi)定義
@Test
class Demo1 {
    //使用自Test 注解修飾屬性
    @Test
    var name: String = ""

    //使用自Test 注解修飾方法 
    @Test
    fun info() {

    }
}

如果要用注解來(lái)修飾主構(gòu)造器,就像前面所介紹的肠骆,程序必須為主構(gòu)造器添加 constructor 關(guān)鍵字算途。

class User @Test constructor(var name : String, var pass: String) { }

注解的屬性和構(gòu)造器

注解還可以帶屬性,由于注解沒(méi)有注解體蚀腿,因此注解的屬性只能在注解聲明部分指定嘴瓤。實(shí)際上,相當(dāng)于在注解的主構(gòu)造器中指定注解的屬性莉钙。

由于注解與普通類(lèi)不同 廓脆, 注解的屬性值只能在使用時(shí)指定,并且一旦為注解的屬性指定了屬性值磁玉,以后就絕對(duì)不會(huì)改變其屬性值停忿,因此注解的屬性只能定義為只讀屬性。

annotation class MyTag(val name: String , val age : Int)

使用 annotation class 定義的注解其實(shí)就相當(dāng)于定義了一個(gè)注解接口蚊伞,這個(gè)注解接口繼承了kotlin.Annotation接口瞎嬉。
需要說(shuō)明的是,注解的屬性不能使用可空類(lèi)型(不能在類(lèi)型后添加“?”)厚柳,這是因?yàn)镴VM本身不允許使用 null作為注解的屬性值氧枣。

一旦在注解中定義了屬性之后 ,使用該屬性時(shí)就應(yīng)該為其指定屬性值 别垮,如下面代碼所示

class Item {
    //使用帶屬性的注解時(shí)便监,需要為屬性指定屬性值 
    @MyTag(name="xx", age=6)
    fun info() {
    }
}

也可以在定義注解的屬性時(shí)使用等號(hào)(=)為其指定初始值(默認(rèn)值)(就像定義類(lèi)時(shí)在主構(gòu)造器中為類(lèi)的屬性指定初始值 一樣) ,注解的初始值只能是編譯時(shí)常量碳想。如果為注解的屬性指定了默認(rèn)值烧董,那么在使用該注解時(shí)可以不為這些屬性指定值,而是直接使用默認(rèn)值胧奔。

根據(jù)注解是否可以包含屬性逊移,可以把注解分為如下兩類(lèi)。

  • 標(biāo)記注解: 沒(méi)有定義屬性的注解被稱(chēng)為標(biāo)記注解龙填。這種注解僅利用自身的存在與否來(lái)提供信息胳泉,如前面所介紹的@Test等注解。
  • 元數(shù)據(jù)注解: 包含屬性的注解被稱(chēng)為元數(shù)據(jù)注解岩遗。因此它們可以接受更多的配置信息(以屬性值的方式進(jìn)行設(shè)置) 扇商。 如前面所介紹的@MyTag等注解。

與 Java類(lèi)似的是宿礁,如果注解的屬性名為 value案铺,則為 value屬性指定屬性值時(shí)可省略屬性名。

Kotlin使用 vararg修飾需要指定多個(gè)值的屬性(相當(dāng)于數(shù)組類(lèi)型的屬性),也可以不帶屬性名梆靖。

如果將一個(gè)注解作為另一個(gè)注解的屬性值控汉,那么在使用注解時(shí)不需要以@作為前綴笔诵。

//定義帶 value 屬性的注解
annotation class MyTag(val value: String)

//該注解的 target 屬性的類(lèi)型是 MyTag
annotation class showTag(val message:String,val tag:MyTag)


@showTag(message = "SS",tag = MyTag("ZZZ"))
class Demo1

如果需要將一個(gè)類(lèi)作為注解的屬性,請(qǐng)使用 Kotlin 類(lèi)( KClass), Kotlin 編譯器會(huì)自動(dòng)將 其轉(zhuǎn)換為 Java類(lèi)姑子,以便 Java代碼能夠正赤头牛看到該注解和參數(shù) 。

// tag1 的類(lèi)型是 KClass<*>壁酬,這是星號(hào)投影用法,相當(dāng)于 Java 的原始類(lèi)型
// tag2 的類(lèi)型是 KClass<out Any>恨课,這是使用處協(xié)變的用法
//可傳入 KClass<Int>舆乔、 KClass<String〉等,只要尖括號(hào)里的類(lèi)型是 Any 的子類(lèi)即可
annotation class DrawTag(val tag1:KClass<*>,val tag2:KClass<out Any>)

@DrawTag(tag1 = String::class,tag2 = Int::class)
class Demo1

元注解

Kotlin 在 kotlin.annotation 包下提供了4個(gè)Meta 注解(元注解)剂公,這4個(gè)元注解都用于修飾其他的注解定義希俩。

使用@ Retention

@Retention只能修飾注解定義,用于指定被修飾的注解可以保留多長(zhǎng)時(shí)間 纲辽。@Retention元注解包含一個(gè) AnnotationRetention類(lèi)型的 value屬性颜武,所以使用@Retention時(shí)必須為該value 屬性指定值。
value 屬性的值只能是如下 3 個(gè) 拖吼。

  • AnnotationRetention.SOURCE: 注解只保留在源代碼中鳞上,編譯器直接丟棄這種注解 。
  • AnnotationRetention.BINARY: 編譯器將把注解記錄在 class 文件中 吊档。當(dāng)運(yùn)行該字節(jié)碼
    文件時(shí)篙议, JVM 不可獲取注解信息。
  • AnnotationRetention.RUNTIME: 編譯器將把注解記錄在 class文件中怠硼。當(dāng)運(yùn)行該字節(jié)
    碼文件時(shí)鬼贱, JVM也可獲取注解信息,程序可以通過(guò)反射獲取該注解信息香璃。這是默認(rèn)值这难。

如果要通過(guò)反射獲取注解信息,就需要使用 value屬性值為 AnnotationRetention.RUNTIME
的@Retention (或省略該元注解)葡秒。使用@Retention元注解可采用如下代碼為value指定值姻乓。

//下面定義的 Testable 注解保留到運(yùn)行時(shí)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class Test

使用@Target

@Target 也只能修飾注解定義,用于指定被修飾的注解能修飾哪些程序單元眯牧。@Target 元注解包含一個(gè)類(lèi)型為 AnnotationTarget 數(shù)組的 allowedTargets 屬性糖权,該屬性的值只能是如下幾個(gè)值組成的數(shù)組。

  • Annotation Target.CLASS: 指定該策略的注解只能修飾類(lèi)炸站。
  • AnnotationTarget.ANNOTATION_CLASS:指定該策略的注解只能修飾注解星澳。
  • AnnotationTarget.TYPE_PARAMETER:指定該策略的注解只能修飾泛型形參(目前暫時(shí)還不支持)。
  • AnnotationTarget.PROPERTY:指定該策略的注解只能修飾屬性旱易。
  • AnnotationTarget.FIELD: 指定該策略的注解只能修飾字段(包括屬性的幕后字段)禁偎。
  • AnnotationTarget.LOCAL_VARIABLE:指定該策略的注解只能修飾局部變量腿堤。
  • AnnotationTarget.VALUE_PARAMETER:指 定該策略的注解只能修飾函數(shù)或構(gòu)造器的形參。
  • AnnotationTarget.CONSTRUCTOR: 指定該策略的注解只能修飾構(gòu)造器如暖。
  • AnnotationTarget.FUNCTION: 指定該策略的注解只能修飾函數(shù)和方法(不包含構(gòu)造器)笆檀。
  • AnnotationTarget. PROPERTY_GETTER:指 定該策略的注解只能修飾屬性的getter 方法。
  • AnnotationTarget. PROPERTY_SETTER:指 定該策略的注解只能修飾屬性的setter 方法盒至。
  • Annotation Target.TYPE: 指定該策略的注解只能修飾類(lèi)型酗洒。
  • AnnotationTarget.EXPRESSION: 指定該策略的注解只能修飾各種表達(dá)式。
  • AnnotationTarget.FILE: 指定該策略的注解只能修飾文件枷遂。
  • AnnotationTarget.TYPEALIAS:指定該策略的注解只能修飾類(lèi)型別名樱衷。

與使用@Retention 類(lèi)似的是,使用@Target 也可以直接在括號(hào)里指定value 值酒唉,而無(wú)須使用name=value 的形式矩桂。如下代碼指定@ActionListenerFor注解只能修飾屬性。

//指定@ActionListenerFor注解只能修飾屬性
@Target(allowedTargets = AnnotationTarget.PROPERTY)
annotation class ActionListenerFor

使用@MustBeDocumented

使用@MustBeDocumented元注解修飾的注解將被文檔工具提取到API文檔中痪伦,如果定義注解類(lèi)時(shí)使用了@MustBeDocumented修飾侄榴,則所有使用該元注解修飾的程序元素的API文檔中將會(huì)包含該注解說(shuō)明 。
下面代碼定義了一個(gè)@Testable 注解网沾,程序使用@MustBeDocumented 修飾該注解癞蚕,所以@Testable 注解將被文檔工具所提取。

//指定@ActionListenerFor注解只能修飾屬性
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定義@Testable 注解將被文檔工具所提取
@MustBeDocumented
annotation class Testable

上面代碼指定了文檔工具生成 API 文檔時(shí)將提取@Testable的使用信息辉哥。下面代碼定義了一個(gè) MyTest類(lèi)涣达,該類(lèi)中的 info()方法使用了@Testable修飾。

class MyTest {
   //使用自Testable修飾info()方法
   @Testable
   private fun info() {
       println("info")

   }
}

使用@Repeatable標(biāo)記可重復(fù)注解

Kotlin允許使用多個(gè)相同的注解來(lái)修飾同一個(gè)程序單元证薇,這種注解稱(chēng)為可重復(fù)注解 度苔。
開(kāi)發(fā)可重復(fù)注解需要使用@Repeatable修飾,下面通過(guò)示例來(lái)介紹如何開(kāi)發(fā)可重復(fù)注解浑度。首先定義一個(gè)@FkTag注解寇窑。

//指定@ActionListenerFor注解只能修飾屬性
@Retention(value = AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
@Repeatable
annotation class FkTag(val name:String="kotlin",val age:Int)

上面定義了@FkTag注解,該注解包含兩個(gè)屬性箩张。程序還使用了@Repeatable 來(lái)修飾該注解甩骏,這意味著它是一個(gè)可重復(fù)注解,因此可直接使用多個(gè)@FkTag 注解修飾目標(biāo)程序單元先慷。

@FkTag(name = "xq",age = 24)
@FkTag(age = 4)
class MyTest {
    private fun info() {
        println("info")

    }
}

需要說(shuō)明的是饮笛,由于在Java 8 之前JVM 并不支持可重復(fù)注解,Kotlin 也沒(méi)有辦法突破該限制论熙,因此可重復(fù)注解的@Retention 策略只能指定為 AnnotationRetention.SOURCE福青,這意味著可重復(fù)注解只能被 Kotlin 編譯器讀取,接下來(lái) Kotlin 編譯器會(huì)直接丟棄該注解信息。

使用注解

提取注解信息

使用注解修飾類(lèi)无午、方法媒役、屬性等成員之后,這些注解不會(huì)自己生效宪迟,必須由開(kāi)發(fā)者提供相應(yīng)的工具來(lái)提取并處理注解信息酣衷。
Kotlin使用 kotlin.Annotation接口來(lái)代表程序元素前面的注解,該接口是所有注解的父接口次泽。Kotlin 在kotlin.reflect 包下新增了KAnnotatedElement接口穿仪,該接口代表程序中可以接受注解的程序元素。該接口主要有如下幾個(gè)實(shí)現(xiàn)類(lèi)意荤。

  • KCallable: 代表可執(zhí)行的程序?qū)嶓w啊片,如函數(shù)和屬性。
  • KClass:代表 Kotlin 的類(lèi)袭异、接口等類(lèi)型。
  • KParameter: 代表函數(shù)和屬性的參數(shù)炬藤。

在kotlin.reflect包下主要包含一些實(shí)現(xiàn)反射功能的工具類(lèi)御铃,該包所提供的反射API包含了讀取運(yùn)行時(shí)注解的能力。只有當(dāng)定義注解時(shí)使用了@Retention(AnnotationRetention.RUNTIME) 修飾沈矿,該注解才會(huì)保留到程序運(yùn)行時(shí)上真,JVM 才會(huì)在裝載* .class 文件時(shí)讀取保存在 class 文件中的注解。

KAnnotatedElement接口是所有程序元素(如 KClass羹膳、 KCallable睡互、 KParameter)的父接口, 所以程序通過(guò)反射獲取了某個(gè)程序單元對(duì)應(yīng)的KAnnotatedElement對(duì)象(如 KClass陵像、KCallable就珠、 KParameter)之后,程序就可以調(diào)用該對(duì)象的如下屬性和方法來(lái)訪(fǎng)問(wèn)注解信息醒颖。

  • annotations: List<Annotation>: 該屬性返回該程序單元上所有的注解妻怎。
  • <T: Annotation> findAnnotation(): T?: 根據(jù)注解類(lèi)型返回該程序單元上特定類(lèi)型的注解。如果該類(lèi)型的注解不存在泞歉,則該方法返回 null逼侦。

下面代碼片段用于獲取 Test類(lèi)中修飾 info()方法的所有注解,井將這些注解打印出來(lái)腰耙。

//指定@ActionListenerFor注解只能修飾屬性
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定義@Testable 注解將被文檔工具所提取
@MustBeDocumented
annotation class Testable


class MyTest {
    //使用自Testable修飾info()方法
    @Testable
    public fun info() {
        println("info")

    }
}

fun main(args: Array<String>) {
    val aArray = MyTest::info.annotations
    //遍歷所有注解
    for (an in aArray){
        println(an)
    }
}

如果需要獲取某個(gè)注解里的元數(shù)據(jù)榛丢,則可以將注解轉(zhuǎn)型成所需的注解類(lèi)型,然后通過(guò)注解對(duì)象的屬性來(lái)訪(fǎng)問(wèn)這些元數(shù)據(jù)挺庞。代碼如下 :

//指定@ActionListenerFor注解只能修飾屬性
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定義@Testable 注解將被文檔工具所提取
@MustBeDocumented
annotation class Testable(val name:String="xq",val age:Int =24)


class MyTest {
    //使用自Testable修飾info()方法
    @Testable(name = "sq",age = 23)
    fun info() {
        println("info")

    }
}

fun main(args: Array<String>) {
    val aArray = MyTest::info.annotations
    //遍歷所有注解
    for (an in aArray){
        println(an)
        if(an is Testable){
            println(an.name)
            println(an.age)
        }
    }
}

下面分別介紹兩個(gè)使用注解的例子晰赞。第 一個(gè)例子中的@Testable 注解沒(méi)有任何屬性,它僅是一個(gè)標(biāo)記注解,其作用是標(biāo)記哪些方法需要測(cè)試宾肺。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定義一個(gè)標(biāo)記注解溯饵,不包含任何屬性
annotation class Testable

class Test {
    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m1() {

    }

    fun m2() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m3() {

    }

    fun m4() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m5() {

    }

    fun m6() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m7() {

    }

    fun m8() {

    }
}

正如前面所提到的,僅僅使用注解來(lái)標(biāo)記程序元素對(duì)程序是不會(huì)有任何影響的锨用,這也是注解的一條重要原則丰刊。為了讓程序中的這些注解起作用,接下來(lái)必須為這些注解提供一個(gè)注解處理工具增拥。

下面的注解處理工具會(huì)分析目標(biāo)類(lèi)啄巧,如果目標(biāo)類(lèi)中的方法使用了@Testable 注解修飾,則通過(guò)反射來(lái)運(yùn)行該測(cè)試方法掌栅。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定義一個(gè)標(biāo)記注解秩仆,不包含任何屬性
annotation class Testable

class Test {
    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m1() {

    }

    fun m2() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m3() {
        throw RuntimeException ("參數(shù)出錯(cuò)了! ")
    }

    fun m4() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m5() {

    }

    fun m6() {

    }

    //使用@ Testable 注解指定該方法是需要測(cè)試的
    @Testable
    fun m7() {
        throw RuntimeException ("程序業(yè)務(wù)出現(xiàn)異常! ")
    }

    fun m8() {

    }
}

inline fun <reified T : Any> processTestable() {
    var  passed =0
    var failed = 0
    val target = T::class.createInstance()
    //遍歷 T 對(duì)應(yīng)的類(lèi)里的所有方法
    for (m in T::class.functions) {
        //如果該方法使用了@ Testable 修飾
        if (m.findAnnotation<Testable>() != null) {
            try{
                //調(diào)用 m方法
                m.call(target)
                //測(cè)試成功, passed 計(jì)數(shù)器加 1
                passed++
            }catch (ex:Exception){
                println ("方法"+ m +"運(yùn)行失敗猾封,異常:" + ex.cause)
                //測(cè)試出現(xiàn)異常澄耍, failed 計(jì)數(shù)器加 1
                failed++

            }
        }
    }

    //統(tǒng)計(jì)測(cè)試結(jié)果
    println ("共運(yùn)行了:"+ (passed + failed) +"個(gè)方法,其中:\n"+"失敗了:"+ failed +"個(gè)晌缘,\n" +"成功了:"+ passed +"個(gè)!")
}

fun main(args: Array<String>) {
    //處理 MyTest 類(lèi)
    //運(yùn)行結(jié)果:方法fun test10.Test.m3(): kotlin.Unit運(yùn)行失敗齐莲,異常:java.lang.RuntimeException: 參數(shù)出錯(cuò)了! 
    //方法fun test10.Test.m7(): kotlin.Unit運(yùn)行失敗,異常:java.lang.RuntimeException: 程序業(yè)務(wù)出現(xiàn)異常! 
    //共運(yùn)行了:4個(gè)方法磷箕,其中:
    //失敗了:2個(gè)选酗,
    //成功了:2個(gè)!
    processTestable<Test>()
}

上面程序定義了 一個(gè)<reifiedT: Any> processTestable()函數(shù), 該函數(shù)可接收一個(gè)泛型參數(shù)岳枷, 分析該泛型參數(shù)所代表的類(lèi)芒填,并運(yùn)行該目標(biāo)類(lèi)中使用@Testable修飾的方法。

前面介紹的只是一個(gè)標(biāo)記注解空繁,程序通過(guò)判斷該注解存在與否來(lái)決定是否運(yùn)行指定方法殿衰。下面程序通過(guò)使用注解來(lái)簡(jiǎn)化事件編程。在傳統(tǒng)的事件編程中總是需要通過(guò)addActionListener() 方法來(lái)為事件源綁定事件監(jiān)聽(tīng)器盛泡,本示例則通過(guò)@ActionListenerFor 來(lái)為程序中的按鈕綁定事件監(jiān)聽(tīng)器播玖。

package test10

import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.WindowConstants.EXIT_ON_CLOSE
import kotlin.reflect.KClass

//指定該注解只能修飾屬性
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
//定義一個(gè)屬性,用于設(shè)置元數(shù)據(jù)
//該 listener 屬性用于保存監(jiān)聽(tīng)器實(shí)現(xiàn)類(lèi)
annotation class ActionListenerFor(val listener: KClass<out ActionListener>)

class AnnotationTest {
    val mainWin = JFrame("使用注解綁定事件監(jiān)聽(tīng)器")
    //使用注解為 ok 按鈕綁定事件監(jiān)聽(tīng)器
    @ActionListenerFor(listener = OkListener::class)
    val ok = JButton("確定")

    //使用注解為 cancel 按鈕綁定事件監(jiān)聽(tīng)器
    @ActionListenerFor(listener = CancelListener::class)
    val cancel = JButton("取消")

    fun init() {
        //初始化界面的方法
        val jp = JPanel()
        jp.add(ok)
        jp.add(cancel)
        mainWin.add(jp)
        processAnnotations(this)
        mainWin.defaultCloseOperation = EXIT_ON_CLOSE
        mainWin.pack()
        mainWin.isVisible = true
    }
}

//定義 ok 按鈕的事件監(jiān)聽(tīng)器實(shí)現(xiàn)類(lèi)
class OkListener : ActionListener {
    override fun actionPerformed(e: ActionEvent?) {
        JOptionPane.showMessageDialog(null, "單擊了確認(rèn)按鈕")
    }

}

//定義cancel按鈕的事件監(jiān)聽(tīng)器實(shí)現(xiàn)類(lèi)
class CancelListener : ActionListener {
    override fun actionPerformed(e: ActionEvent?) {
        JOptionPane.showMessageDialog(null, "單擊了取消按鈕")
    }

}

fun main(args: Array<String>) {
    AnnotationTest().init()
}

//處理注解的方法饭于,其中 obj 是包含注解的對(duì)象
fun processAnnotations(obj: Any) {
    //獲取 obj 對(duì)象的類(lèi)
    val cl = obj::class
    //獲取指定 obj對(duì)象的所有成員蜀踏,并遍歷每個(gè)成員
    for (prop in cl.memberProperties) {
        //獲取該成員上 ActionListenerFor 類(lèi)型的注解
        val a = prop.findAnnotation<ActionListenerFor>()
        //獲取屬性 prop 的值
        val fObj = prop.call(obj)

        //如果 fObj 是 AbstractButton的實(shí)例,且 a 不為 null
        if (a != null && fObj != null && fObj is AbstractButton) {
            //獲取 a 注解的 listener 屬性值〈它是一個(gè)監(jiān)聽(tīng)器類(lèi)〉
            val listenerClazz = a.listener
            //使用反射來(lái)創(chuàng)建 listener 類(lèi)的對(duì)象
            val al = listenerClazz.createInstance()
            //為 fObj 按鈕添加事件監(jiān)聽(tīng)器
            fObj.addActionListener(al)
        }

    }
}

上面代碼定義了兩個(gè) JButton 按鈕掰吕,并使用@ActionListenerFor 注解為這兩個(gè)按鈕綁定了事件監(jiān)聽(tīng)器果覆。使用@ActionListenerFor 注解時(shí)傳入了 listener 元數(shù)據(jù),該元數(shù)據(jù)用于設(shè)定每個(gè)按鈕的監(jiān)聽(tīng)器實(shí)現(xiàn)類(lèi)殖熟。

正如前面所提到的局待,如果僅在程序中使用注解是不會(huì)起任何作用的,必須使用注解處理工具來(lái)處理程序中的注解。 上面代碼使用了 processAnnotations()函數(shù)來(lái)處理注解钳榨,該處理器分析目標(biāo)對(duì)象中的所有屬性舰罚,如果在屬性前使用了@ActionListenerFor 修飾,則取出該注解中的listener元數(shù)據(jù)薛耻,并根據(jù)該元數(shù)據(jù)來(lái)綁定事件監(jiān)聽(tīng)器营罢。

Java 注解與 Kotlin 的兼容性

Java 注解與 Kotlin 完全兼容,只是在使用時(shí)略加注意即可饼齿。

指定注解的作用目標(biāo)

根據(jù)前面的介紹我們知道饲漾, Kotlin程序往往比較簡(jiǎn)潔, Kotlin程序的一個(gè)程序單元有時(shí)候會(huì)變成Java的多個(gè)程序單元缕溉。比如:

  • 帶屬性聲明的主構(gòu)造器會(huì)變成 Java 的成員變量定義考传、 getter方法、 setter方法(如果是讀寫(xiě)屬性)证鸥、構(gòu)造器參數(shù) 僚楞。
  • 屬性會(huì)變成 Java 的成員變量定義、 getter方法枉层、 setter方法(如果是讀寫(xiě)屬性) 泉褐。

這樣就產(chǎn)生了一個(gè)問(wèn)題: 有時(shí)候我們只想用注解修飾特定的程序單元,比如只希望用注解修飾屬性對(duì)應(yīng)的幕后字段返干,或者只希望用注解修飾屬性對(duì)應(yīng)的getter方法兴枯,那該怎么辦呢?

此時(shí)就需要為注解指定作用目標(biāo)血淌,語(yǔ)法格式如下:

@目標(biāo) :注解(注解屬性值)

如果在同 一個(gè)目標(biāo)上要指定多個(gè)注解矩欠,則需要將多個(gè)注解放在方括號(hào)中,并用空格隔開(kāi)悠夯,語(yǔ)法格式如下:

@目標(biāo) : [注解 1 (注解屬性值)注解 2 (注解屬性值)癌淮, . . . ]

從上面的語(yǔ)法格式不難看出,為注解指定作用目標(biāo)沦补,其實(shí)就是在@符號(hào)和注解之間添加目標(biāo)名和冒號(hào)乳蓄。 Kotlin支持的目標(biāo)包含如下幾個(gè)。

  • file: 指定注解對(duì)文件本身起作用 夕膀。
  • property: 指定注解對(duì)整個(gè)屬性起作用(這種目標(biāo)的注解對(duì) Java 不可見(jiàn)虚倒,因?yàn)?Java 并沒(méi)有真正的屬性) 。
  • field: 指定注解對(duì)屬性的幕后字段起作用产舞。
  • get: 指定注解對(duì)屬性的 getter方法起作用魂奥。
  • set: 指定注解對(duì)屬性的 setter方法起作用 。
  • receiver: 指定注解對(duì)擴(kuò)展方法或擴(kuò)展屬性的接收者起作用 易猫。
  • param: 指定注解對(duì)構(gòu)造器的參數(shù)起作用耻煤。
  • setparam: 指定注解對(duì) setter方法的參數(shù)起作用。
  • delegate: 指定注解對(duì)委托屬性存儲(chǔ)其委托實(shí)例的字段起作用。

下面先看一個(gè)簡(jiǎn)單的例子哈蝇,程序指定注解只對(duì)屬性的 getter方法起作用棺妓。

annotation class MyTag
annotation class FkTag(val info: String)

class Item {
    //指定注解只對(duì) getter 方法起作用
    //對(duì) getter 方法應(yīng)用了兩個(gè)注解: MyTag、 FkTag
    @get:[MyTag FkTag(info = "補(bǔ)充信息")]
    var name = "kotlin"
}

fun main(args: Array<String>) {

    //獲取Item類(lèi)對(duì)應(yīng)的Java類(lèi) (Class對(duì)象)
    val clazz = Item::class.java
    //遍歷 clazz 類(lèi)所包含的全部方法
    for (mtd in clazz.declaredMethods) {
        println("一方法${mtd}上的注解如下一")
        //遍歷該方法上直接聲明的所有注解
        for (an in mtd.declaredAnnotations) {
            println(an)
        }
    }

    //遍歷 clazz 類(lèi)所包含的全部成員變量
    for (f in clazz.declaredFields) {
        println("一屬性${f}上的注解如下一")
        //遍歷該成員變盤(pán)上直接聲明的所有注解
        for (an in f.declaredAnnotations) {
            println(an)
        }
    }
}

上面代碼指定 name屬性的 getter方法應(yīng)用了兩個(gè)注解炮赦,其實(shí)就是在原來(lái)的注解用法前增加@get:部分怜跑,與原來(lái)不指定目標(biāo)的注解區(qū)別并不大。

程序后面的 main()函數(shù)主要就是分析 Item 類(lèi)中各方法眼五、成員變量的注解 妆艘。 由于要分析成員變量上的注解,因此 main()函數(shù)使用了 Java 的反射 API (由于 Kotlin 并不支持單獨(dú) 定義成員變量看幼,因此 Kotlin 的反射 API 不支持直接操作成員變量)批旺,通過(guò)該 main()的運(yùn)行可以看到程序中添加的兩個(gè)注解只作用于屬性的getter方法上 。

如果要指定注解作用于整個(gè)文件本身诵姜,則必須將注解放在 package 語(yǔ)句(如果有 package語(yǔ)句)之前汽煮,或者所有導(dǎo)包語(yǔ)句之前(如果沒(méi)有package 語(yǔ)句) 。代碼如下 :

//指定自 FileTag 注解作用于整個(gè)文件
@file: FileTag ("yeeku") 
package org.crazyit.demo

如果要指定注解作用于擴(kuò)展方法或擴(kuò)展屬性的接收者棚唆,則使用帶 receiver:的注解修飾整個(gè)擴(kuò)展方法或擴(kuò)展屬性即可暇赤。例如如下代碼:

// 指定@MyTag 注解作用于擴(kuò)展方法的接收者(String) 
fun @receiver:MyTag String.fun() {}

使用Java注解

Kotlin完全兼容Java注解,因此可以直接在 Kotlin程序中使用Java注解宵凌。
需要說(shuō)明的是鞋囊,Java注解的成員變量 (相當(dāng)于 Kotlin 注解的屬性,后文統(tǒng)稱(chēng)為“屬性”) 是沒(méi)有順序的瞎惫,因此只能通過(guò)屬性名來(lái)設(shè)置屬性值 CKotlin注解的屬性還可通過(guò)位置來(lái)設(shè)置屬性值) 溜腐。

對(duì)比如下兩個(gè)注解。 下面先定義一個(gè) Java注解瓜喇。

public @interface JavaTag {
    String name();

    int age();
}

下面再定義一個(gè) Kotlin注解挺益。

annotation class KotlinTag(val name:String,val age:Int)

上面兩個(gè)注解基本相同,它們都包含了 name 和 age 兩個(gè)屬性 乘寒。接下來(lái)在程序中使用這兩個(gè)注解就可看到它們的區(qū)別 望众。

//Kotlin 注解可通過(guò)位置來(lái)指定屬性值
//第一個(gè)值傳給 name 屬性,第二個(gè)值傳給 age 屬性
@KotlinTag("kotlin", 4)
class Book {
    //Kotlin 注解也可通過(guò)屬性名來(lái)指定屬性值
    @KotlinTag(name = "xq", age = 24)
    //Java注解只能通過(guò)屬性名來(lái)指定屬性值
    @JavaTag(name = "xy", age = 22)
    fun test() {
        
    }
}

Kotlin 注解既支持使用位置來(lái)指定屬性值(第一個(gè)值傳給第一個(gè)屬性伞辛,第二個(gè)值傳給第二個(gè)屬性烂翰,依此類(lèi)推),也支持使用屬性名來(lái)指定屬性值(這是傳統(tǒng) Java注解的使用方式〉;而 Java注解只能通過(guò)屬性名來(lái)指定屬性值 蚤氏。

如果 Java 注解中的 value 屬性是數(shù)組類(lèi)型甘耿,那么它會(huì)變成 Kotlin 注解的 vararg屬性,因此直接為它傳入多個(gè)屬性值即可 瞧捌。

public @interface JavaTag {
    String[] value();
}

數(shù)組類(lèi)型的 value 屬性會(huì)變成 Kotlin 注解的 vararg屬性棵里,因此可以在 Kotlin 程序中按如下方式使用該注解 润文。

@JavaTag("kotlin", "java")
class Book 

但如果其他名稱(chēng)的屬性是數(shù)組類(lèi)型,那么在 Kotlin 中使用該注解時(shí)必須顯式使用 arrayOf() 函數(shù)來(lái)構(gòu)建數(shù)組殿怜。例如如下 Java注解典蝌。

public @interface JavaTag {
    String[] infos();
} 

上面注解的 infos 屬性是數(shù)組類(lèi)型,因此在 Kotlin 中使用該注解時(shí)必須顯式使用 arrayOf() 函數(shù)來(lái)構(gòu)建數(shù)組头谜。例如如下代碼:

@JavaTag(infos=arrayOf("kotlin", "java"))
class Book 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骏掀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柱告,更是在濱河造成了極大的恐慌截驮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件际度,死亡現(xiàn)場(chǎng)離奇詭異葵袭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)乖菱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)坡锡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人窒所,你說(shuō)我怎么就攤上這事鹉勒。” “怎么了吵取?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵禽额,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我皮官,道長(zhǎng)脯倒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任臣疑,我火速辦了婚禮盔憨,結(jié)果婚禮上徙菠,老公的妹妹穿的比我還像新娘讯沈。我一直安慰自己,他們只是感情好婿奔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布缺狠。 她就那樣靜靜地躺著,像睡著了一般萍摊。 火紅的嫁衣襯著肌膚如雪挤茄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天冰木,我揣著相機(jī)與錄音穷劈,去河邊找鬼笼恰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛歇终,可吹牛的內(nèi)容都是我干的社证。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼评凝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼追葡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起奕短,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宜肉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后翎碑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谬返,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年日杈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朱浴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡达椰,死狀恐怖翰蠢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啰劲,我是刑警寧澤梁沧,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蝇裤,受9級(jí)特大地震影響廷支,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栓辜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一恋拍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藕甩,春花似錦施敢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至腋妙,卻和暖如春默怨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骤素。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工匙睹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愚屁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓痕檬,卻偏偏與公主長(zhǎng)得像集绰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谆棺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 人生苦多栽燕,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin改淑! 什么是Kotlin碍岔? Kotlin 是種靜態(tài)類(lèi)型編程...
    任半生囂狂閱讀 26,201評(píng)論 9 118
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,163評(píng)論 0 2
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來(lái) What:A...
    zlcook閱讀 29,158評(píng)論 15 116
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2朵夏、Retrofit解析...
    隔壁老李頭閱讀 6,461評(píng)論 4 31
  • 我送月亮回A市的時(shí)候正是春寒料峭的日子仰猖,她拖著一只大大的行李箱捏肢,里面胡亂塞滿(mǎn)了各式各樣的當(dāng)季或是不當(dāng)季的衣服,既有...
    有山初白閱讀 636評(píng)論 2 3