Kotlin中的反射

ScottSong發表於2019-03-03

部落格地址sguotao.top

Java中的反射機制,使得我們可以在執行期獲取Java類的位元組碼檔案中的建構函式,成員變數,成員函式等資訊。這一特性使得反射機制被常常用在框架中,想要比較系統的瞭解Kotlin中的反射,先從Java的反射說起。

Java中的反射

通常我們寫好的.java原始碼檔案,經過javac的編譯,最終生成了.class位元組碼檔案。這些位元組碼檔案是與平臺無關的,使用時通過Classloader去載入這些.class位元組碼檔案,從而讓程式按照我們編寫好的業務邏輯執行。Java的反射主要是從這些.class檔案中獲取我們想要得到的內容,那麼Java中的反射能夠得到哪些內容呢?

獲取Class物件

Java是物件導向的語言,同樣的.class位元組碼檔案也不例外,想要獲取.class檔案中的內容,就要先獲取.class檔案對應的Class物件。Java中獲取Class物件的方式有三種。

//1.Class.forName("類名字串") (注意:類名字串必須是全稱,包名+類名)
Class baseInfo = Class.forName("top.sguotao.ReflectionJava");
 //2.類名.class
Class object = Object.class;
//3.例項物件.getClass()
Class date = (new Date()).getClass();
Class testclass = this.getClass();
複製程式碼

獲取類的建構函式Constructor

獲取Class物件之後,就可以獲取其中的建構函式,從而去建立例項物件。類的建構函式對應java.lang.reflect.Constructor。獲取建構函式歸納起來有以下五種方式:

    // 1.獲取引數列表是parameterTypes,訪問控制符是public的建構函式
    public Constructor getConstructor(Class[] parameterTypes)

    // 2.獲取所有訪問控制符是public的建構函式
    public Constructor[] getConstructors()

    // 3.獲取引數列表是parameterTypes,並且是類自身宣告的建構函式,訪問控制符包含public、protected和private的函式。
    public Constructor getDeclaredConstructor(Class[] parameterTypes)

    //4.獲取類自身宣告的全部的建構函式,訪問控制符包含public、protected和private的函式。
    public Constructor[] getDeclaredConstructors()

    //5.如果類宣告在其它類的建構函式中,返回該類所在的建構函式,如果存在則返回,不存在返回null
    public Constructor getEnclosingConstructor()
複製程式碼

獲取類的成員變數

類的成員變數對應的是java.lang.reflect.Field,獲取成員變數歸納起來有以下四種方式:

    //1.獲取“名稱是name”的public的成員變數(包括從基類繼承的、從介面實現的所有public成員變數)
    public Field getField(String name)

    //2.獲取全部的public成員變數(包括從基類繼承的、從介面實現的所有public成員變數)
    public Field[] getFields()

    //3.獲取“名稱是name”,並且是類自身宣告的成員變數,包含public、protected和private成員變數。
    public Field getDeclaredField(String name)

    //4.獲取全部的類自身宣告的成員變數,包含public、protected和private成員變數。
    public Field[] getDeclaredFields()
複製程式碼

獲取類的成員函式

類的成員函式對應的是java.lang.reflect.Method,獲取成員函式歸納起來有下面5種方式:

// 1.獲取函式名是name,引數是parameterTypes的public的函式(包括從基類繼承的、從介面實現的所有public函式)
    public Method getMethod(String name, Class[] parameterTypes)

    //2.獲取全部的public的函式(包括從基類繼承的、從介面實現的所有public函式)
    public Method[] getMethods()

    //3.獲取函式名name,引數是parameterTypes,並且是類自身宣告的函式,包含public、protected和private方法。
    public Method getDeclaredMethod(String name, Class[] parameterTypes)

    //4.獲取全部的類自身宣告的函式,包含public、protected和private方法。
    public Method[] getDeclaredMethods()

    //5.如果這個類是其它類中某個方法的內部類,呼叫getEnclosingMethod()就是這個類所在的方法;若不存在,返回null。
    public Method getEnclosingMethod()
複製程式碼

獲取類的其它資訊

獲取類的註解資訊,對應的是java.lang.annotation.Annotation介面,獲取類的註解資訊歸納起來有下面3種方法:

//1.獲取類的annotationClass型別的註解 (包括從基類繼承的、從介面實現的所有public成員變數)
public Annotation<A> getAnnotation(Class annotationClass)

//2.獲取類的全部註解 (包括從基類繼承的、從介面實現的所有public成員變數)
public Annotation[] getAnnotations()

//3.獲取類自身宣告的全部註解 (包含public、protected和private成員變數)
public Annotation[] getDeclaredAnnotations()
複製程式碼

獲取類的介面和基類的資訊,對應的是java.lang.reflect.Type介面,獲取類的介面和基類資訊有下面兩個方法:

 // 獲取實現的全部介面
public Type[] getGenericInterfaces()

// 獲取基類
public Type getGenericSuperclass()
複製程式碼

獲取類的其它描述資訊。

    //1.獲取類名
    public String getSimpleName()

    //2.獲取完整類名
    public String getName()

    //3.判斷類是不是列舉類
    public boolean isEnum()

    //4.判斷obj是不是類的例項物件
    public boolean isInstance(Object obj)

    //5.判斷類是不是介面
    public boolean isInterface()

    //6.判斷類是不是本地類,所謂本地類,就是定義在方法內部的類。
    public boolean isLocalClass()

    //7.判斷類是不是成員類,所謂成員類,就是常見的內部類,是指不在程式碼塊,建構函式和成員方法中的內部類。
    public boolean isMemberClass()

    //8.判斷類是不是基本型別。 基本型別,包括void和boolean、byte、char、short、int、long、float 和 double這幾種型別。
    public boolean isPrimitive()
複製程式碼

Kotlin中的反射

在Kotlin中,位元組碼對應的類是kotlin.reflect.KClass,因為Kotlin百分之百相容Java,所以Kotlin中可以使用Java中的反射,但是由於Kotlin中位元組碼.class對應的是KClass類,所以如果想要使用Java中的反射,需要首先獲取Class的例項,在Kotlin中可以通過以下兩種方式來獲取Class例項。

//1.通過例項.javaClass
var hello = HelloWorld()
hello.javaClass

 //2.通過類Kclass類的.java屬性
HelloWorld::class.java
複製程式碼

獲取了Class例項,就可以呼叫上面介紹的方法,獲取各種在Java中定義的類的資訊了。

當然Kotlin中除了可以使用Java中的反射以外,還可以使用Kotlin中宣告的一些方法,當然同Java中反射一樣,想要使用這些方法,先要獲取Kclass物件,在Kotlin中可以通過以下兩種方式獲取Kclass例項。

 //1.通過類::class的方式獲取Kclass例項
val clazz1: KClass<*> = HelloWorld::class
//2.通過例項.javaClass.kotlin獲取Kclass例項
var hello = HelloWorld()
val clazz2 = hello.javaClass.kotlin
複製程式碼

獲取了Kclass例項之後,就可以呼叫Kotlin中宣告的一些關於反射的方法了,那麼都有哪些方法呢?

建構函式Constructor

Kotlin可以通過下面的方法,獲取所有的建構函式。

//返回這個類的所有構造器
public val constructors: Collection<KFunction<T>>
複製程式碼

成員變數和成員函式

Kotlin中獲取成員變數和成員函式的方法有6個。

    //返回類可訪問的所有函式和屬性,包括繼承自基類的,但是不包括構造器
    override val members: Collection<KCallable<*>>
    //返回類宣告的所有函式
    val KClass<*>.declaredFunctions: Collection<KFunction<*>>
    //返回類的擴充套件函式
    val KClass<*>.declaredMemberExtensionFunctions: Collection<KFunction<*>>
    //返回類的擴充套件屬性
    val <T : Any> KClass<T>.declaredMemberExtensionProperties: Collection<KProperty2<T, *, *>>
    //返回類自身宣告的成員函式
    val KClass<*>.declaredMemberFunctions: Collection<KFunction<*>>
    //返回類自身宣告的成員變數(屬性)
    val <T : Any> KClass<T>.declaredMemberProperties: Collection<KProperty1<T, *>>
複製程式碼

類相關資訊

可以看到Kotlin反射中,可以獲取比Java反射更多的關於類的資訊。

//1.返回類的名字
public val simpleName: String?
 //2.返回類的全包名
public val qualifiedName: String?
//3.如果這個類宣告為object,則返回其例項,否則返回null
public val objectInstance: T?
//4.返回類的可見性
@SinceKotlin("1.1")
public val visibility: KVisibility?
//5.判斷類是否為final類(在Kotlin中,類預設是final的,除非這個類宣告為open或者abstract)
@SinceKotlin("1.1")
public val isFinal: Boolean
//6.判斷類是否是open的(abstract類也是open的),表示這個類可以被繼承
@SinceKotlin("1.1")
public val isOpen: Boolean
//7.判斷類是否為抽象類
@SinceKotlin("1.1")
public val isAbstract: Boolean
//8.判斷類是否為密封類,密封類:用sealed修飾,其子類只能在其內部定義
@SinceKotlin("1.1")
public val isSealed: Boolean
//9.判斷類是否為data類
@SinceKotlin("1.1")
public val isData: Boolean
//10.判斷類是否為成員類
@SinceKotlin("1.1")
public val isInner: Boolean
//11.判斷類是否為companion object
@SinceKotlin("1.1")
public val isCompanion: Boolean    
//12.返回類中定義的其他類,包括內部類(inner class宣告的)和巢狀類(class宣告的)
public val nestedClasses: Collection<KClass<*>>
 //13.判斷一個物件是否為此類的例項
@SinceKotlin("1.1")
public fun isInstance(value: Any?): Boolean
//14.返回這個類的泛型列表
@SinceKotlin("1.1")
public val typeParameters: List<KTypeParameter>
//15.類其直接基類的列表
@SinceKotlin("1.1")
public val supertypes: List<KType>
//16.返回類所有的基類
val KClass<*>.allSuperclasses: Collection<KClass<*>>
//17.返回類的伴生物件companionObject
val KClass<*>.companionObject: KClass<*>?
複製程式碼

使用Kotin中反射注意的問題

在Kotlin1.1中如果反射String,Map,List等型別時,會丟擲一個built-in Kotlin Types的異常,這是因為在Kotlin1.1版本中還沒有對這些型別新增支援,在Kotlin1.2版本中,這個問題已經解決。

Kotlin關於反射的內容都放在kotlin-reflect的jar包中,這個jar包有2.6M,對於移動端開發,需要佔用一定的記憶體空間。

最後就是關於Kotlin反射的效率問題,在Java中反射大概需要幾十微秒,在Kotlin就需要幾百甚至上千微秒,如果是通過反射訪問物件或構造屬性,甚至需要上萬微秒,對此,官方給出的解釋是,現在還沒有精力進行優化,相信後續的版本中,效率問題會有所改善。

參考連結

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans

相關文章