Kotlin注解與反射的定義及創(chuàng)建使用詳解
1.注解
1.定義
注解是將元數(shù)據(jù)附加到代碼的地方。從字面意思理解它就是對(duì)知識(shí)點(diǎn)的補(bǔ)充,一種描述。在Java中最常見(jiàn)的注解就是@Override或者就是Retrofit中的@GET、@POST等。
2.注解的創(chuàng)建
創(chuàng)建時(shí)用annotation修飾符進(jìn)行聲明,如
annotation class GET()
這樣就創(chuàng)建好了一個(gè)注解,但是這里想要完全使用還要添加一些屬性:
- @Target:指定可以用該注解標(biāo)注的元素的可能的類(lèi)型(類(lèi)、函數(shù)、屬性、表達(dá)式);
- @Retention:指定該注解是否存儲(chǔ)在編譯后的class文件中以及它在運(yùn)行時(shí)能否通過(guò)反射可見(jiàn),默認(rèn)為true;
- @Repeatable:允許在單個(gè)元素上多次使用相同的該注解;
- @MustBeDocumented:指定該注解是公有API的一部分,并且應(yīng)該包含在生成的API文檔中顯示的類(lèi)或方法的簽名中,一般用于SDK文檔中。
這里重點(diǎn)要注意的是 @Target和@Retention
//@Target
public enum class AnnotationTarget {
// 類(lèi)、接口、object、注解類(lèi)
CLASS,
// 注解類(lèi)
ANNOTATION_CLASS,
// 泛型參數(shù)
TYPE_PARAMETER,
// 屬性
PROPERTY,
// 字段、幕后字段
FIELD,
// 局部變量
LOCAL_VARIABLE,
// 函數(shù)參數(shù)
VALUE_PARAMETER,
// 構(gòu)造器
CONSTRUCTOR,
// 函數(shù)
FUNCTION,
// 屬性的getter
PROPERTY_GETTER,
// 屬性的setter
PROPERTY_SETTER,
// 類(lèi)型
TYPE,
// 表達(dá)式
EXPRESSION,
// 文件
FILE,
// 類(lèi)型別名
TYPEALIAS
}
//@Retention
public enum class AnnotationRetention {
// 注解只存在于源代碼,編譯后不可見(jiàn)
SOURCE,
// 注解編譯后可見(jiàn),運(yùn)行時(shí)不可見(jiàn)
BINARY,
// 編譯后可見(jiàn),運(yùn)行時(shí)可見(jiàn) 默認(rèn)
RUNTIME
}
3.注解的使用
@Target(AnnotationTarget.FUNCTION) //注解用于方法
@Retention(AnnotationRetention.RUNTIME) //運(yùn)行時(shí)可見(jiàn),編譯時(shí)可見(jiàn)
annotation class Custom()
//正常使用不報(bào)錯(cuò)
@Custom
fun test() {
println("")
}
//報(bào)錯(cuò),因?yàn)樽⒔獠恢С諧lass,如果要支持就需要在@Target里面加上AnnotationTarget.CLASS
@Custom
class Test{
}
上面的代碼是一個(gè)自定義且最簡(jiǎn)單的一個(gè)用法,現(xiàn)在看一下Kotlin中自帶的一個(gè)注解,這個(gè)注解用來(lái)標(biāo)注廢棄的方法或者類(lèi)等定義,比較常見(jiàn)
@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
)
@Tageget:支持類(lèi)、 函數(shù)、 屬性、注解類(lèi)、構(gòu)造器、屬性 getter、屬性 setter、類(lèi)型別名
在Deprecated內(nèi)部還傳遞了幾個(gè)參數(shù):
- message:對(duì)廢棄內(nèi)容的提示信息
- repleaceWith:表示用什么內(nèi)容來(lái)替代被廢棄的內(nèi)容。需要注意的是后面的
ReplaceWith也是一個(gè)注解,也就是說(shuō)Kotlin中注解中是可以添加注解的,只不過(guò)添加時(shí)不可以用@。 - level:警告程度,有
WARNING、ERROR、HIDDEN
注解中允許的參數(shù)有:
- 對(duì)應(yīng)于 Java 原生類(lèi)型的類(lèi)型(Int、 Long等)
- 字符串
- 類(lèi)(Foo::class)
- 枚舉
- 其他注解
- 上面已列類(lèi)型的數(shù)組
注解參數(shù)不能有可空類(lèi)型,因?yàn)?JVM 不支持將 null 作為注解屬性的值存儲(chǔ)。
2.反射
1.定義
反射是指計(jì)算機(jī)程序在運(yùn)行時(shí)(runtime)可以訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的一種能力。用比喻來(lái)說(shuō),反射就是程序在運(yùn)行的時(shí)候能夠“觀察”并且修改自己的行為。
反射在業(yè)務(wù)開(kāi)發(fā)中用的較少,主要是在架構(gòu)設(shè)計(jì)中,可以極大地提升架構(gòu)的靈活性。
Kotlin的反射具備三個(gè)特點(diǎn):
- 感知程序的狀態(tài),包含程序的運(yùn)行狀態(tài)以及源代碼結(jié)構(gòu);
- 修改程序的狀態(tài);
- 根據(jù)程序的狀態(tài),調(diào)整自身的決策行為。
2.反射的應(yīng)用
首先要加入一個(gè)依賴(lài)才可以使用反射
implementation "org.jetbrains.kotlin:kotlin-reflect"
然后根據(jù)上面提到的三個(gè)特點(diǎn)進(jìn)行講解:
- 感知程序的狀態(tài):
舉例:假設(shè)現(xiàn)在有兩個(gè)對(duì)象,在不傳遞具體對(duì)象的前提下想要打印出他們每一個(gè)屬性的名稱(chēng)以及具體的值
fun main() {
val person = Person("張三", 22)
val animal = Animal("貓", "用腳走", "貓糧")
findClassAttribute(person)
findClassAttribute(animal)
}
fun findClassAttribute(obj: Any) {
}
data class Person(val name: String, var age: Int)
data class Animal(var species: String, val walkWay: String, var food: String)
//期望結(jié)果:
//Person.name = 張三
//Person.age = 22
//Animal.species = 貓
//Animal.walkWay = 用腳走
//Animal.food = 貓糧
上面只是定義了兩個(gè)類(lèi),具體項(xiàng)目中可能會(huì)很有很多的類(lèi),因此用when的方式是行不通的因?yàn)檫@樣工作量還是比較大的,那么用反射反而是一個(gè)比較好的方式,那要如何實(shí)現(xiàn)?
fun findClassAttribute(obj: Any) {
obj::class.memberProperties.forEach {
println("${obj::class.simpleName}.${it.name} = ${it.getter.call(obj)}")
}
}
//輸出結(jié)果
//Person.age = 22
//Person.name = 張三
//Animal.food = 貓糧
//Animal.species = 貓
//Animal.walkWay = 用腳走
看到這個(gè)是不是一臉懵?這是啥鬼東西。我們對(duì)上面的代碼進(jìn)行分析就明白了:
- obj::class: 這是類(lèi)引用,是Kotlin的反射語(yǔ)法,通過(guò)這樣的語(yǔ)法可以獲取變量的類(lèi)型信息并且可以拿到這個(gè)變量的類(lèi)型KClass,也就是我們的
Person和Animal。 - memberProperties: 通過(guò)前面的
obj::class拿到了具體的類(lèi)型,那么也就拿到了這個(gè)這個(gè)類(lèi)型的所有信息,比如說(shuō)simpleName、constructors,而memberProperties就是獲取類(lèi)的成員屬性,然后通過(guò)foreach遍歷出來(lái)就好了。 - it:KProperty1: 這里的KProperty1是KClass的子類(lèi),通過(guò)
it.name拿到屬性的命名,it.getter.call拿到屬性的值。
經(jīng)過(guò)上述幾個(gè)關(guān)鍵信息就獲取到了我們想要的輸出結(jié)果,這就是感知程序的狀態(tài)。
- 修改程序狀態(tài)
拿到每個(gè)屬性的命名和值之后我想要修改動(dòng)物類(lèi)的某個(gè)屬性的值怎么辦?增加一個(gè)changeClassAttributeValue方法用來(lái)修改屬性值
fun changeClassAttributeValue(obj: Any) {
obj::class.memberProperties.forEach {
if (it.name == "food" //判斷要修改的屬性名是【food】
&& it is KMutableProperty1 //判斷這個(gè)屬性是否可以被修改,val屬性不可被修改,var屬性可以
&& it.setter.parameters.size == 2 //修改屬性需要setter,我們要先判斷 setter 的參數(shù)是否符合預(yù)期,這里 setter 的參數(shù)個(gè)數(shù)應(yīng)該是 2,第一個(gè)參數(shù)是 obj 自身,第二個(gè)是實(shí)際的值
&& it.getter.returnType.classifier == String::class //判斷要修改的屬性是不是string類(lèi)型
) {
it.setter.call(obj, "雞胸肉") //修改屬性值
println("========屬性值修改========")
}
}
}
fun main() {
val person = Person("張三", 22)
val animal = Animal("貓", "用腳走", "貓糧")
changeClassAttributeValue(animal)
findClassAttribute(animal)
}
//輸出結(jié)果
//Animal.food = 雞胸肉
//Animal.species = 貓
//Animal.walkWay = 用腳走
根據(jù)屬性值food將貓糧修改為雞胸肉。 這種操作方式就是反射的第二個(gè)特點(diǎn):修改程序的狀態(tài)
- 根據(jù)程序的狀態(tài),調(diào)整自身的決策行為。
上面我們已經(jīng)調(diào)整狀態(tài)了,那么我還想加一個(gè)修改屬性:species,吃雞胸肉的也可以是小狗,因此我們只需要再加一個(gè)else即可,這樣就實(shí)現(xiàn)了最后一個(gè)特點(diǎn):根據(jù)程序的狀態(tài),調(diào)整自身的決策行為。
fun changeClassAttributeValue(obj: Any) {
obj::class.memberProperties.forEach {
if (it.name == "food" //判斷要修改的屬性名是【food】
&& it is KMutableProperty1 //判斷這個(gè)屬性是否可以被修改
&& it.setter.parameters.size == 2 //修改屬性需要setter,我們要先判斷 setter 的參數(shù)是否符合預(yù)期,這里 setter 的參數(shù)個(gè)數(shù)應(yīng)該是 2,第一個(gè)參數(shù)是 obj 自身,第二個(gè)是實(shí)際的值
&& it.getter.returnType.classifier == String::class //判斷要修改的屬性是不是string類(lèi)型
) {
it.setter.call(obj, "雞胸肉")
println("======== food 屬性值修改========")
} else if (it.name == "species" //判斷要修改的屬性名是【species】
&& it is KMutableProperty1 //判斷這個(gè)屬性是否可以被修改,val屬性不可被修改,var屬性可以
&& it.setter.parameters.size == 2 //修改屬性需要setter,我們要先判斷 setter 的參數(shù)是否符合預(yù)期,這里 setter 的參數(shù)個(gè)數(shù)應(yīng)該是 2,第一個(gè)參數(shù)是 obj 自身,第二個(gè)是實(shí)際的值
&& it.getter.returnType.classifier == String::class //判斷要修改的屬性是不是string類(lèi)型
) {
it.setter.call(obj, "小狗")
println("======== species 屬性值修改========")
} else { // 差別在這里
println("沒(méi)找到相關(guān)屬性")
}
}
}
fun main() {
val person = Person("張三", 22)
val animal = Animal("貓", "用腳走", "貓糧")
changeClassAttributeValue(animal)
findClassAttribute(animal)
}
//輸出結(jié)果
//Animal.food = 雞胸肉
//Animal.species = 小狗
//Animal.walkWay = 用腳走
這里還要說(shuō)明的是可修改的屬性值一定是用var修飾的,如果在demo過(guò)程中出現(xiàn)不能修改的要檢查屬性聲的明是否可修改。
上面的代碼通過(guò)memberProperties進(jìn)入之后可以發(fā)現(xiàn)它用到了 Kotlin 反射的幾個(gè)關(guān)鍵類(lèi):KClass、KCallable、KParameter、KType?,F(xiàn)在,我們來(lái)進(jìn)一步看看它們的關(guān)鍵成員
KClass 代表了一個(gè) Kotlin 的類(lèi),下面是它的重要成員:
- simpleName: 獲取類(lèi)名稱(chēng),如果是匿名內(nèi)部類(lèi)獲取的值為null;
- qualifiedName: 完整的類(lèi)名。用【.】分隔
- members: 可訪問(wèn)的所有函數(shù)和屬性,類(lèi)型為
Collection<KCallable<*>>; - constructors: 獲取所有構(gòu)造函數(shù),類(lèi)型為
Collection<KFunction<T>>; - nestedClasses: 獲取聲明的所有類(lèi)包含內(nèi)部嵌套類(lèi)和嵌套靜態(tài)類(lèi),類(lèi)型為
Collection<KClass<*>>; - objectInstance: 對(duì)象聲明的實(shí)例如果這個(gè)類(lèi)不是對(duì)象聲明則返回null;
- typeParameters: 獲取參數(shù)類(lèi)型列表,但不包括外部類(lèi)的參數(shù)類(lèi)型,類(lèi)型為
List<KTypeParameter>; - supertypes: 該類(lèi)的直接超類(lèi)型列表,按它們?cè)谠创a中列出的順序排列,類(lèi)型為
List<KType>; - sealedSubclasses: 如果是密封類(lèi)則直接獲取子類(lèi)的列表否則為空,類(lèi)型為
List<KClass<out T>>; - visibility: 類(lèi)的可見(jiàn)性,如果可見(jiàn)性不能在Kotlin中表示則為null;
- isFinal: 返回該類(lèi)是否是
final類(lèi)型,類(lèi)型為Boolean,如果是則為true; - isOpen: 返回該類(lèi)是否是
open修飾的,類(lèi)型為Boolean,如果是則為true; - isAbstract: 返回該類(lèi)是否是
abstract的,類(lèi)型為Boolean,如果是則為true; - isSealed: 返回該類(lèi)是否是密封類(lèi),類(lèi)型為Boolean,如果是則為true;
- isData: 返回該類(lèi)是否是數(shù)據(jù)類(lèi),類(lèi)型為Boolean,如果是則為true;
- isInner: 返回該類(lèi)是否是內(nèi)部類(lèi),類(lèi)型為Boolean,如果是則為true;
- isCompanion: 返回該類(lèi)是否是伴生對(duì)象,類(lèi)型為Boolean,如果是則為true;
- isFun: 返回該類(lèi)是否是函數(shù)式接口,類(lèi)型為Boolean,如果是則為true;
- isValue: 返回該類(lèi)是否是Value Class,類(lèi)型為Boolean,如果是則為true。
KCallable 代表了 Kotlin 當(dāng)中的所有可調(diào)用的元素,比如函數(shù)、屬性、甚至是構(gòu)造函數(shù)。下面是 KCallable 的重要成員:
- name: 獲取可調(diào)用的聲明的名稱(chēng),如果可調(diào)用對(duì)象沒(méi)有名稱(chēng)則創(chuàng)建一個(gè)特殊的名稱(chēng),沒(méi)有名稱(chēng)包括:
構(gòu)造函數(shù)名稱(chēng)為"";
屬性訪問(wèn)器:一個(gè)名為foo的屬性getter將會(huì)有名稱(chēng),同理setter將會(huì)有名稱(chēng)
- parameters : 獲取所有可調(diào)用的參數(shù),如果需要this實(shí)例或者擴(kuò)展接收方參數(shù)那么他們通過(guò)List返回,返回類(lèi)型為
List<KParameter>; - returnType : 獲取返回值的類(lèi)型,返回類(lèi)型為
KType; - typeParameters : 獲取可調(diào)用參數(shù)的類(lèi)型以列表返回,返回類(lèi)型為
List<KTypeParameter>; - visibility : 元素的可見(jiàn)性,如果可見(jiàn)性不能在Kotlin中表示則為null;
- isFinal : 返回該元素是否是
final修飾的,類(lèi)型為Boolean,如果是則為true; - isOpen : 返回該元素是否是
open修飾的,類(lèi)型為Boolean,如果是則為true; - isAbstract : 返回該元素是否是
abstract修飾的,類(lèi)型為Boolean,如果是則為true; - isSuspend : 返回該元素是否是掛起函數(shù),類(lèi)型為Boolean,如果是則為true。
KParameter,代表了KCallable當(dāng)中的參數(shù),它的重要成員如下:
- index: 參數(shù)在參數(shù)列表中的索引,從0開(kāi)始;
- name: 參數(shù)聲明的名稱(chēng),如果沒(méi)有名稱(chēng)或者名稱(chēng)在運(yùn)行時(shí)不可用則返回null;
- type: 參數(shù)類(lèi)型,對(duì)于可變參數(shù)參數(shù)類(lèi)型是數(shù)組而不是單個(gè)元素,返回類(lèi)型為
KType; - kind: 參數(shù)的種類(lèi)
INSTANCE: 對(duì)象的實(shí)例;
EXTENSION_RECEIVER: 擴(kuò)展接收者;
VALUE: 具體的值;
- isOptional: 如果此參數(shù)是可選的,則為true,當(dāng)通過(guò)KCallable進(jìn)行調(diào)用時(shí)可以省略此參數(shù)。callBy,否則為false。參數(shù)可選的情況如下:
默認(rèn)值在該參數(shù)的聲明中提供;
形參在成員函數(shù)中聲明,并且在超函數(shù)中有一個(gè)對(duì)應(yīng)的形參是可選的。
- isVararg: 如果參數(shù)為可變長(zhǎng)度則返回true。
KType,代表了 Kotlin 當(dāng)中的類(lèi)型,它重要的成員如下:
- classifier: 類(lèi)型對(duì)應(yīng)Kotlin類(lèi),即KClass,如果不能在Kotlin中表示則返回null;
- arguments: 是該類(lèi)型中的分類(lèi)器的參數(shù)傳遞的類(lèi)型參數(shù),就是泛型。
- isMarkedNullable: 是否被標(biāo)記為可空類(lèi)型,就是后面有沒(méi)有【?】。
這幾個(gè)類(lèi)集合了很多個(gè)API,了解每一個(gè)的作用之后再了解反射就會(huì)很簡(jiǎn)單了。

以上就是Kotlin注解與反射的定義及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin注解反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android打造流暢九宮格抽獎(jiǎng)活動(dòng)效果
抽獎(jiǎng)活動(dòng)有很多種形式,轉(zhuǎn)盤(pán)抽獎(jiǎng),九宮格抽獎(jiǎng),刮刮卡抽獎(jiǎng),這篇文章主要為大家詳細(xì)介紹了如何打造流暢九宮格抽獎(jiǎng)活動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android檢測(cè)手機(jī)中存儲(chǔ)卡及剩余空間大小的方法(基于Environment,StatFs及DecimalFormat
這篇文章主要介紹了Android檢測(cè)手機(jī)中存儲(chǔ)卡及剩余空間大小的方法,基于Environment,StatFs及DecimalFormat實(shí)現(xiàn)該功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01
Android實(shí)現(xiàn)隨意拖動(dòng)View效果的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)隨意拖動(dòng)View效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
Android應(yīng)用內(nèi)存泄漏優(yōu)化指南
內(nèi)存泄漏(Memory Leak)是指應(yīng)用中不再使用的對(duì)象因錯(cuò)誤引用無(wú)法被垃圾回收(GC),導(dǎo)致內(nèi)存占用持續(xù)增長(zhǎng),最終可能引發(fā) OOM(Out Of Memory)崩潰?或 應(yīng)用卡頓,以下是 Android 內(nèi)存泄漏的優(yōu)化方案,涵蓋檢測(cè)工具、常見(jiàn)場(chǎng)景及解決方案,需要的朋友可以參考下2025-03-03
Android開(kāi)發(fā)使用UncaughtExceptionHandler捕獲全局異常
本文主要介紹在Android開(kāi)發(fā)中使用UncaughtExceptionHandler捕獲全局異常,需要的朋友可以參考下。2016-06-06
Android使用WindowManager制作一個(gè)可拖動(dòng)的控件
這篇文章主要為大家詳細(xì)介紹了Android使用WindowManager制作一個(gè)可拖動(dòng)的控件的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-08-08
Android實(shí)現(xiàn)Tab切換界面功能詳解
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)Tab切換界面的功能,以及對(duì)Tab變化事件進(jìn)行監(jiān)聽(tīng)。文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-05-05

