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