定義注解
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