Java 反射 VS Kotlin 反射

Kotlin 跟 Java 可以無縫銜接,因此 Kotlin 能夠使用 Java 的反射機制。另外,Kotlin 也有自己的反射機制,需要額外地引入 kotlin-reflect.jar。

implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

kotlin-reflect.jar 中包含 kotlin.reflect.full 和 kotlin.reflect.jvm。

  • kotlin.reflect.full 是主要的 Kotlin 反射 API
  • kotlin.reflect.jvm 用於 Kotlin 反射和 Java 反射的互操作。

Kotlin 反射的特性包含:

  • 提供對屬性和可空類型的訪問權限,這是由於 Java 沒有屬性和可空類型的概念。
  • Kotlin 反射不是 Java 反射的替代品,而是功能的增強。
  • 可以使用 Kotlin 反射來訪各種基於 JVM 語言編寫的代碼。

下面以 Java 的反射和 Kotlin 的反射進行對比。

一. 類引用,獲取 Class 對象

Java 獲取 Class 對象的方式

  • Class.forName("完整的包名+類名")
Class<?> clazz = Class.forName("xxx.xxx.MyClass")
  • 類名.class
Class<?> clazz = MyClass.class;
  • 實例對象.getClass()
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();

Kotlin 獲取 Class 對象的方式

  • 調用的類是 Java 類,需要添加 .java 後綴( KClass 的擴展屬性 java)
val clazz = MyClass::class.java
  • 添加 Java 實例對象的.javaClass後綴(Java 實例對象的擴展屬性 javaClass)。
val obj = MyClass()
val clazz = obj.javaClass
  • 調用的類是 Kotlin 類
val clazz = MyClass::class

此時 clazz 的類型是 KClass 類型,KClass 的一個實例表示對 Kotlin 類的引用。KClass 也是 Kotlin 反射 API 的主要入口。

在 Kotlin 中,字節碼對應的類也是 kotlin.reflect.KClass。

Kotlin 的引用類有兩種方式:類名::class對象::class,它們獲取的都是相同的 KClass 實例。

即處於同一個類加載器中,給定的類型只能返回一個 KClass 實例。即使多次嘗試實例化 KClass, 仍然只能獲取同一對象的引用, Kotlin 不會創建新的引用。

二. 構造函數引用,獲取類的構造函數

Java 獲取類的構造函數

Java 在獲取 Class 實例之後,可以獲取其中的構造函數。Java 獲取類的構造函數對應的是 java.lang.reflect.Constructor,有以下五種方式:

// 獲取參數列表是 parameterTypes,訪問 public 的構造函數
public Constructor getConstructor(Class[] parameterTypes)

// 獲取所有 public 構造函數
public Constructor[] getConstructors()

// 獲取參數列表是 parameterTypes,並且是類自身聲明的構造函數,訪問控制符包含 public、protected 和 private 的函數。
public Constructor getDeclaredConstructor(Class[] parameterTypes)

// 獲取類自身聲明的全部的構造函數,包含 public、protected和private 的函數。
public Constructor[] getDeclaredConstructors()

// 如果類聲明在其它類的構造函數中,返回該類所在的構造函數,如果存在則返回,不存在返回null
public Constructor getEnclosingConstructor()

Kotlin 獲取類的構造函數

Kotlin 在獲取 KClass 實例之後,可以獲取它的全部構造函數。

// 類中聲明的所有構造函數。
public val constructors: Collection<KFunction<T>>

Kotlin 通過::操作符並添加類名來引用構造函數。例如:

class Foo()

fun function(action: () -> Foo) {
    val foo: Foo = action.invoke()
    println(foo) // Foo@66d3c617
}

fun main() {
    function(::Foo)
}

對於含有參數的構造函數,也一樣適用:

class Foo(val x:String)

fun main() {
    val foo = ::Foo // 表示引用 Foo 類的構造函數
    val f = foo("test")
    println(f)
}

三. 函數引用,獲取類的成員函數

Java 獲取類的成員函數

Java 獲取類的成員函數對應的是 java.lang.reflect.Method,有以下五種方式:

// 根據函數名 name,參數 parameterTypes 獲取類自身的 public 的函數(包括從基類繼承的、從接口實現的所有 public 函數)
public Method getMethod(String name, Class[] parameterTypes)

// 獲取全部 public 的函數(包括從基類繼承的、從接口實現的所有 public 函數)
public Method[] getMethods()

// 根據函數名 name,參數 parameterTypes,獲取類自身聲明的函數,包含 public、protected 和 private 方法。
public Method getDeclaredMethod(String name, Class[] parameterTypes)

// 獲取類自身聲明的函數,包含 public、protected 和 private 方法。
public Method[] getDeclaredMethods()

// 如果此 Class 對象表示某一方法中的一個本地或匿名類,則返回 Method 對象,它表示底層類的立即封閉方法。若不存在,返回 null。
public Method getEnclosingMethod()

Kotlin 獲取類的成員函數

Kotlin 通過反射調用函數,需要 KFunction 實例。KFunction 實例可以通過兩種方式獲得:一種是方法引用,另一種是通過 KClass 提供的 API 獲得 KFunction 實例。

方法引用是簡化版本的 Lambda 表達式,它和 Lambda 表達式擁有相同的特性。

Kotlin 和 Java 的方法引用使用::操作符,Kotlin 除了可以引用類中成員函數、擴展函數還可以引用頂層(top-level)函數。

方法引用屬於 KFunction 的子類型 KFunctionN,N 代表具體的參數數量,例如 KFunction2<T1, T2, R>。KFunction 與 Lambda 的 Function 類似,都可以通過invoke()方法調用該引用函數。

例如:

val sum = {
        x: Int, y: Int -> x + y
}

fun sumFunction(x: Int, y: Int) = x + y

fun main() {
    println(sum(3,5))

    val sunFunc:KFunction2<Int,Int,Int> = ::sumFunction
    println(sunFunc.invoke(3,5))

    println(sunFunc.call(3, 5))
}

執行結果:

8
8
8

KFunctionN 類型屬於合成的編譯器生成類型,我們無法在包 kotlin.reflect 中找到它們的聲明。

在上述例子中,最後一行代碼:

println(sunFunc.call(3, 5))

其實這裏調用 KCallable 接口的call()方法。

public actual interface KCallable<out R> : KAnnotatedElement {

    ......

    /**
     * Calls this callable with the specified list of arguments and returns the result.
     * Throws an exception if the number of specified arguments is not equal to the size of [parameters],
     * or if their types do not match the types of the parameters.
     */
    public fun call(vararg args: Any?): R

    ......
}

對於 KFunction 對象,也可以使用 KCallable 的call()方法來調用被引用的函數。call()方法使用指定的參數列表,開發者需要自行匹配所使用的實參類型和數量,如果其類型與參數的類型不匹配,則會引發異常。

而 KFunctionN 的invoke()方法的形參類型和返回值類型是可以確定的,調用它的 invoke() 時編譯器會幫我們做檢查。

當然,也可以使用 KFunctionN 來引用類的擴展函數。引用擴展函數的用法跟引用成員函數的用法是一致的。

data class User(var name:String,var password:String)

fun User.validatePassword():Boolean = this.password.length > 6

val validate:KFunction1<User,Boolean> = User::validatePassword

fun main() {

    val user = User("tony","12345")
    println(user.validatePassword())
    println(validate.invoke(user))
    println(validate.call(user))
}

四. 屬性引用,獲取類的成員變量

Java 獲取類的成員變量

Java 獲取類的成員變量對應的是 java.lang.reflect.Field,有以下四種方式:

// 獲取相應的類自身聲明的 public 成員變量(包括從基類繼承的、從接口實現的)
public Field getField(String name)

// 獲取類自身聲明全部的 public 成員變量(包括從基類繼承的、從接口實現的)
public Field[] getFields()

// 獲取相應的類自身聲明的成員變量,包含 public、protected 和 private 成員變量。
public Field getDeclaredField(String name)

// 獲取類自身聲明的成員變量,包含 public、protected 和 private 。
public Field[] getDeclaredFields()

Kotlin 獲取類的屬性

Kotlin 沒有成員變量的概念,只有屬性的概念。下面展示了引用不同屬性的方式。

不可變屬性的引用

Kotlin 使用::屬性來獲取不可變屬性的引用,並返回 KProperty<V> 類型的值。它的get()方法會返回屬性的值,它的name屬性會返回可變屬性的名稱。

val x = 1
val y = "hello"

fun main() {
    println(::x)
    println(::x.get())
    println(::x.name)
    println(::y)
    println(::y.get())
    println(::y.name)
}

執行結果:

val x: kotlin.Int
1
x
val y: kotlin.String
hello
y
可變屬性的引用

Kotlin 使用::屬性來獲取可變屬性的引用,並返回 KMutableProperty<V> 類型的值。它除了上述的get()方法、name屬性,還支持set()方法修改屬性的值。

var x = 1
var y = "hello"

fun main() {
    println(::x)
    ::x.set(0)
    println(::x.get())
    println(::x.name)
    println(::y)
    ::y.set("world")
    println(::y.get())
    println(::y.name)
}

執行結果:

var x: kotlin.Int
0
x
var y: kotlin.String
world
y
擴展屬性的引用

Kotlin 使用類名::屬性來獲取擴展屬性的引用,並返回 KProperty1<T, out V> 類型的值。它的get()getValue()方法會返回擴展屬性的值。

class Extension

val Extension.x: Int
    get() = 1

fun main() {
    println(Extension::x) // Extension.x: kotlin.Int
    val extension = Extension()
    println(Extension::x.get(extension)) // 1
    println(Extension::x.getValue(extension,Extension::x)) // 1
}
類的成員屬性的引用

Kotlin 使用類名::屬性來獲取成員屬性的引用,並返回 KProperty1<T, out V> 類型的值。它的get()call()方法會返回成員屬性的值。

class Bar1(val x: Int)
class Bar2(val x: Int,val y:String)

fun main() {
    val bar1 = Bar1(1)
    val prop = Bar1::x
    println(prop.call(bar1))
    println(prop.getter.call(bar1))
    println(prop.get(bar1))

    val bar2 = Bar2(2,"test")
    val propX = Bar2::x
    val propY = Bar2::y
    println(propX.call(bar2))
    println(propY.call(bar2))
    println(propX.get(bar2))
}

執行結果:

1
1
1
2
test
2

另外,這裏的call()方法,依然是 KCallable 接口的call()方法。call()方法的調用最終會調用該屬性的 getter 屬性。

畢竟,KProperty、KFunction 的超類型都是 KCallable

五. 獲取類的其它信息

Java 獲取類的其它信息

Java 獲取類的註解信息,對應的是 java.lang.annotation.Annotation 接口,有以下三種方式:

// 獲取類的 annotationClass 類型的註解 (包括從基類繼承的、從接口實現的所有 public 成員變量)
public Annotation<A> getAnnotation(Class annotationClass)

// 獲取類的全部註解 (包括從基類繼承的、從接口實現的所有 public 成員變量)
public Annotation[] getAnnotations()

// 獲取類自身聲明的全部註解 (包含 public、protected 和 private 成員變量)
public Annotation[] getDeclaredAnnotations()

Java 獲取類的接口和基類的信息,對應的是 java.lang.reflect.Type 接口,有以下兩種方式:

// 獲取類實現的全部接口
public Type[] getGenericInterfaces()

// 獲取類的直接超類的 Type
public Type getGenericSuperclass()

Java 獲取類的其它描述信息,包括:

// 獲取類名
public String getSimpleName()

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

// 判斷類是不是枚舉類
public boolean isEnum()

// 判斷obj是不是類的實例對象
public boolean isInstance(Object obj)

// 判斷類是不是接口
public boolean isInterface()

// 判斷類是不是局部類。局部類所屬範圍:在塊、構造器以及方法內,這裏的塊包括普通塊和靜態塊。
public boolean isLocalClass()

// 判斷類是不是成員類
public boolean isMemberClass()

// 判斷類是不是基本類型。
public boolean isPrimitive()

Kotlin 獲取類的其它信息

Kotlin 能夠獲取更多的類的信息,包括:

// 獲取類的名字
public val simpleName: String?

 // 獲取類的全包名
public val qualifiedName: String?

// 如果這個類聲明爲 object,則返回其實例,否則返回 null
public val objectInstance: T?

// 獲取類的可見性
@SinceKotlin("1.1")
public val visibility: KVisibility?

// 判斷類是否爲 final 類,Kotlin 默認類是 final 類型的,除非這個類聲明爲 open 或者 abstract
@SinceKotlin("1.1")
public val isFinal: Boolean

// 判斷類是否是open,(abstract  類也是 open),表示這個類可以被繼承
@SinceKotlin("1.1")
public val isOpen: Boolean

// 判斷類是否爲抽象類
@SinceKotlin("1.1")
public val isAbstract: Boolean

// 判斷類是否爲密封類
@SinceKotlin("1.1")
public val isSealed: Boolean

// 判斷類是否爲 data class
@SinceKotlin("1.1")
public val isData: Boolean

// 判斷類是否爲成員類
@SinceKotlin("1.1")
public val isInner: Boolean

// 判斷類是否爲 companion object
@SinceKotlin("1.1")
public val isCompanion: Boolean

// 判斷類是否爲 functional interface
@SinceKotlin("1.4")
public val isFun: Boolean

// 獲取類中定義的其他類,包括內部類和嵌套類
public val nestedClasses: Collection<KClass<*>>

// 判斷一個對象是否爲此類的實例
@SinceKotlin("1.1")
public fun isInstance(value: Any?): Boolean

// 獲取這個類的泛型列表
@SinceKotlin("1.1")
public val typeParameters: List<KTypeParameter>

// 獲取類其直接基類的列表
@SinceKotlin("1.1")
public val supertypes: List<KType>

// 如果該類是密封類,返回子類的列表,否則返回空列表。
@SinceKotlin("1.3")
public val sealedSubclasses: List<KClass<out T>>

// 獲取類所有的基類
val KClass<*>.allSuperclasses: Collection<KClass<*>>

// 獲取類的伴生對象 companionObject
val KClass<*>.companionObject: KClass<*>?

六. Java 反射與 Kotlin 反射的互操作性

爲一個 Kotlin 屬性獲取一個 Java 的 getter/setter 方法或者幕後字段,需要使用 kotlin.reflect.jvm 包。

幕後字段 (backing field) 是 Kotlin 屬性自動生成的字段,它只能在當前屬性的訪問器(getter、setter)內部使用。

  • KProperty 的擴展屬性 javaGetter:返回給定屬性的 getter 相對應的 Java 方法實例,如果該屬性沒有 getter,則返回 null。
val KProperty<*>.javaGetter: Method?
    get() = getter.javaMethod
  • KProperty 的擴展屬性 javaField:返回給定屬性的幕後字段相對應的 Java 字段實例,如果屬性沒有幕後字段,則返回 null。
val KProperty<*>.javaField: Field?
    get() = this.asKPropertyImpl()?.javaField
  • KMutableProperty 的擴展屬性 javaSetter:返回給定可變屬性的 setter 相對應的 Java 方法實例,如果該屬性沒有 setter,則返回 null。
val KMutableProperty<*>.javaSetter: Method?
    get() = setter.javaMethod

如果要獲取對應於 Java 的 Kotlin 類,使用 .kotlin 擴展屬性返回 KClass 實例。

public val <T : Any> Class<T>.kotlin: KClass<T>
    @JvmName("getKotlinClass")
    get() = Reflection.getOrCreateKotlinClass(this) as KClass<T>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章