认识反射——Kotlin

4,159 阅读5分钟

@(学习笔记)

前几天时间了解了一下关于 Java 反射的 API ,并在文章末尾推荐了Kotlin源码的反射封装库 EasyAndroid ,于是现在就根据 EasyAndroid 来认识一下Kotlin中的反射。

用于进行测试的类:

    
class Test private constructor(private val name:String){
    constructor():this("默认名字")
    constructor(vararg name:String):this(Arrays.toString(name))

    override fun toString(): String {
    return "Test(name='$name')"
    }

    fun invoked() {
    EasyLog.DEFAULT.e("无参数的invoked方法被执行")
    }

    fun invoked(name:String){
    EasyLog.DEFAULT.e("Test的invoked方法被执行。参数为$name")
    }

    fun onlyVararg(vararg name:String) {
    EasyLog.DEFAULT.d(name)
    }

    fun withVararg(perfix:Int, vararg name:String) {
    EasyLog.DEFAULT.d("perfix = $perfix + name = ${name.easyFormat()}")
    }

    companion object {
    @JvmStatic
    private fun print(message:String) {
    EasyLog.DEFAULT.e("静态方法被调用,传入参数:$message")
    }

    @JvmStatic
    private val staticValue = "静态文本"
    }
}

一、类引用


最基本的反射功能是获取 Kotlin 类的运行时引用。要获取对静态已知的 Kotlin 类的引用,可以使用 类字面值 语法:

val t1 = Test::class
Log.e(TAG,"Kotlin 类引用${t1}")
val t2 = Test::class.java
Log.e(TAG,"Java 类引用${t2}")

t1 引用是 KClass 类型的值。但是请注意,Kotlin 类引用与 Java 类引用不同。要获得 Java 类引用, 请在 KClass 实例上使用 .java 属性,如示例中的 t2 ,而 t1 引用在反射中是不可用(Kotlin reflection is not available)

其他类引用方式:

val t3 = Test().javaClass
Log.e(TAG,"getClass 类引用${t3}")
val t4 = Class.forName("com.haoge.sample.easyandroid.activities.Test")
Log.e(TAG,"forName 类引用${t4}")
val t5 = Class.forName("com.haoge.sample.easyandroid.activities.MyClass", false, EasyReflectActivity::class.java.classLoader)
Log.e(TAG,"initialize true")
val t6 = Class.forName("com.haoge.sample.easyandroid.activities.MyClass", true, EasyReflectActivity::class.java.classLoader)

t3 相当于 Java 中的 getClass 方法。 forName(String className) 方法默认会实例化类,而 forName(String name, boolean initialize, ClassLoader loader) 方法则可以通过 initialize 控制是否实例化。对于实例化类不方便在 kotlin 类中测试,于是写了一个 Java 测试类(据说在 kotlin 中没有静态代码块)对于 initialize 的真假效果在于类是否执行静态代码块方法,或者说当 initialize 为真时,相当于类经过了 newInstance 方法。

Java 测试类:

public class MyClass {

    static {
        Log.e("MyClass","loading MyClass ......");
    }
    
}

运行日志:

Kotlin 类引用class com.haoge.sample.easyandroid.activities.Test (Kotlin reflection is not available)
Java 类引用class com.haoge.sample.easyandroid.activities.Test
getClass 类引用class com.haoge.sample.easyandroid.activities.Test
forName 类引用class com.haoge.sample.easyandroid.activities.Test
initialize true
loading MyClass ......

二、类反射


kotlin 类反射同 Java 基本一致。

示例:

//类修饰符 数组
val modifiers = Test::class.java.modifiers
val name = Test::class.java.name
val simpleName = Test::class.java.simpleName
val packageName = Test::class.java.`package`
//规范名称
val canonicalName = Test::class.java.canonicalName
//组件类型
val componentType = Test::class.java.componentType
//已声明 class
val declaredClasses = Test::class.java.declaredClasses
//声明class
val declaringClass = Test::class.java.declaringClass
//内部类
val enclosingClass = Test::class.java.enclosingClass
//        Test::class.java.typeName

//        Test::class.java.toGenericString()
//通用接口
val genericInterfaces = Test::class.java.genericInterfaces
//通用父类
val genericSuperclass = Test::class.java.genericSuperclass
//实现的接口
val interfaces = Test::class.java.interfaces
//继承的父类
val superclass = Test::class.java.superclass
//是否是注解
val isAnnotation = Test::class.java.isAnnotation
//是否是匿名类
val isAnonymousClass = Test::class.java.isAnonymousClass
//是否是枚举
val isEnum = Test::class.java.isEnum
//是否是接口
val isInterface = Test::class.java.isInterface
//是否是成员类
val isMemberClass = Test::class.java.isMemberClass
//是否是原始类型
val isPrimitive = Test::class.java.isPrimitive
//是否是合成类型
val isSynthetic = Test::class.java.isSynthetic

EasyLog.DEFAULT.e("methods:${methods.easyFormat()}\n" +
 "name:${name}\n" +
 "simpleName:${simpleName}\n" +
 "packageName:${packageName}\n" +
 "canonicalName${canonicalName}\n" +
 "componentType:${componentType}\n" +
 "declaredClasses:${declaredClasses}\n" +
 "declaringClass:${declaringClass}\n" +
 "enclosingClass:${enclosingClass}\n" +
 "genericInterfaces:${genericInterfaces}\n" +
 "genericSuperclass:${genericSuperclass}\n" +
 "interfaces:${interfaces}\n" +
 "superclass:${superclass}\n" +
 "isAnnotation:${isAnnotation}\n" +
 "isAnonymousClass:${isAnonymousClass}\n" +
 "isEnum:${isEnum}\n" +
 "isInterface:${isInterface}\n" +
 "isMemberClass:${isMemberClass}\n" +
 "isPrimitive:${isPrimitive}\n" +
 "isSynthetic:${isSynthetic}")

打印日志:

 methods:[Ljava.lang.reflect.Method;@423b3db0
 name:com.haoge.sample.easyandroid.activities.Test
 simpleName:Test
 packageName:package com.haoge.sample.easyandroid.activities
 canonicalNamecom.haoge.sample.easyandroid.activities.Test
 componentType:null
 declaredClasses:[Ljava.lang.Class;@423aefa8
 declaringClass:null
 enclosingClass:null
 genericInterfaces:[Ljava.lang.Class;@423b27c8
 genericSuperclass:class java.lang.Object
 interfaces:[Ljava.lang.Class;@423b3120
 superclass:class java.lang.Object
 isAnnotation:false
 isAnonymousClass:false
 isEnum:false
 isInterface:false
 isMemberClass:false
 isPrimitive:false
 isSynthetic:false

三、方法与构造函数反射剖析


  1. 获取方法名称、修饰符、参数的示例

示例

//获取本类以及父类 public 方法
val methods = Test::class.java.methods
       for(m in methods){
        //方法名称
           val name = m.name
           //修饰符 Android 19 新增方法
           val modifier = getModifiers(m as Executable)
           //参数  Android 26 新增方法
           val parms = getParameters(m as Executable)
           EasyLog.DEFAULT.e("getMethods" +
                   "name:${name}\n" +
                   "modifier:${modifier}\n" +
                   "parms:${parms.easyFormat()}")
       }

//获取本类声明的方法,包括 private 、 protected
 val declaredMethods = Test::class.java.declaredMethods
       for(m in declaredMethods){
        //方法名称
           val name = m.name
           //修饰符
           val modifier = getModifiers(m as Executable)
           //参数
           val parms = getParameters(m as Executable)
           //同上
//            EasyLog.DEFAULT.e("getDeclaredMethods" +
//                    "name:${name}\n" +
//                    "modifier:${modifier}\n" +
//                    "parms:${parms.easyFormat()}")
       }

       //自定义方法获取方法内容
       getCustomMethod()
//方法取自 EasyReflect
 private fun getCustomMethod() {
        val list = mutableListOf<Method>()
        var type: Class<*>? = Test::class.java
        do {
            for (method in type!!.declaredMethods) {
                list.add(method)
            }
            type = type.superclass
        } while (type != null)
        EasyLog.DEFAULT.e("getCustomMethod:${list.easyFormat()}")
    }

    @RequiresApi(Build.VERSION_CODES.KITKAT)
    private fun getModifiers(exec: Executable):String{

        var mod = exec.modifiers
        if (exec is Method) {
            mod = mod and Modifier.methodModifiers()
        } else if (exec is Constructor<*>) {
            mod = mod and Modifier.constructorModifiers()
        }
        return Modifier.toString(mod)
    }


    @RequiresApi(Build.VERSION_CODES.O)
    private fun getParameters(exec: Executable):MutableList<String>{

        val parms = exec.parameters
        val parmList = mutableListOf<String>()
        for (i in 0 until parms.size){

            val mod = parms[i].getModifiers() and Modifier.parameterModifiers()
            val modifiers = Modifier.toString(mod);
            val parmType = parms[i].getType().getSimpleName();
            val parmName = parms[i].getName();
            val temp = modifiers + "  " + parmType + "  " + parmName;
            if(temp.trim().length == 0){
                continue;
            }
            parmList.add(temp.trim());

        }
        return parmList

    }

打印日志:

 getMethodsname:access$getStaticValue$cp
 modifier:public static final
 parms:[
 ]
	getMethodsname:access$super
 modifier:public static
 parms:[
 Test  arg0, 
 String  arg1, 
 Object[]  arg2
 ]
	getMethodsname:equals
 modifier:public
 parms:[
 Object  arg0
 ]
	getMethodsname:getClass
 modifier:public final
 parms:[
 ]
	getMethodsname:hashCode
 modifier:public
 parms:[
 ]
	getMethodsname:invoked
 modifier:public final
 parms:[
 ]
	getMethodsname:invoked
 modifier:public final
 parms:[
 String  arg0
 ]
	getMethodsname:notify
 modifier:public final native
 parms:[
 ]
	getMethodsname:notifyAll
 modifier:public final native
 parms:[
 ]
	getMethodsname:onlyVararg
 modifier:public final
 parms:[
 String[]  arg0
 ]
	getMethodsname:toString
 modifier:public
 parms:[
 ]
	getMethodsname:wait
 modifier:public final native
 parms:[
 ]
	getMethodsname:wait
 modifier:public final
 parms:[
 long  arg0
 ]
	getMethodsname:wait
 modifier:public final native
 parms:[
 long  arg0, 
 int  arg1
 ]
	getMethodsname:withVararg
 modifier:public final
 parms:[
 int  arg0, 
 String[]  arg1
 ]
 getCustomMethod:[
 public static final java.lang.String com.haoge.sample.easyandroid.activities.Test.access$getStaticValue$cp(), 
 public static java.lang.Object com.haoge.sample.easyandroid.activities.Test.access$super(com.haoge.sample.easyandroid.activities.Test,java.lang.String,java.lang.Object[]), 
 private static final java.lang.String com.haoge.sample.easyandroid.activities.Test.getStaticValue(), 
 private static final void com.haoge.sample.easyandroid.activities.Test.print(java.lang.String), 
 public final void com.haoge.sample.easyandroid.activities.Test.invoked(), 
 public final void com.haoge.sample.easyandroid.activities.Test.invoked(java.lang.String), 
 public final void com.haoge.sample.easyandroid.activities.Test.onlyVararg(java.lang.String[]), 
 public java.lang.String com.haoge.sample.easyandroid.activities.Test.toString(), 
 public final void com.haoge.sample.easyandroid.activities.Test.withVararg(int,java.lang.String[]), 
 static int java.lang.Object.identityHashCode(java.lang.Object), 
 private static native int java.lang.Object.identityHashCodeNative(java.lang.Object), 
 private native java.lang.Object java.lang.Object.internalClone(), 
 protected java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException, 
 public boolean java.lang.Object.equals(java.lang.Object), 
 protected void java.lang.Object.finalize() throws java.lang.Throwable, 
 public final java.lang.Class java.lang.Object.getClass(), 
 public int java.lang.Object.hashCode(), 
 public final native void java.lang.Object.notify(), 
 public final native void java.lang.Object.notifyAll(), 
 public java.lang.String java.lang.Object.toString(), 
 public final native void java.lang.Object.wait() throws java.lang.InterruptedException, 
 public final void java.lang.Object.wait(long) throws java.lang.InterruptedException, 
 public final native void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
 ]

调用反射:

调用方法之前,先看看 javaClass 和 class.java 有什么不一样,因为后面我们会用到前者这个关键字。先看测试代码:

val a:Class<Test> = Test::class.java
val b:Class<Test.Companion> = Test.javaClass
EasyLog.DEFAULT.e("class.java:${a.easyFormat()}")
EasyLog.DEFAULT.e("javaclass:${b.easyFormat()}")

打印日志:

class.java:class com.haoge.sample.easyandroid.activities.Test
javaclass:class com.haoge.sample.easyandroid.activities.Test$Companion

上面两种class的区别是什么不知道,但是测试可以作为反射中的 Class

  1. 方法的反射如何实现?

示例:

val cls = Test::class.java
val obj = cls.newInstance()
//调用静态 print 方法
val m1 = cls.getDeclaredMethod("print", String::class.java)
//排除辅助方法检查,使该字段,方法和构造函数可访问。
m1.isAccessible = true
//调用静态方法
m1.invoke(cls,"print内容")
//普通方法 无参
val m2 = cls.getMethod("toString")
val inf0 = m2.invoke(obj)
//打印返回值
EasyLog.DEFAULT.e("toString:${inf0}")
普通方法 有参 (无返回值)
val m3 = cls.getMethod("invoked", String::class.java)
m3.invoke(obj,"有参方法的参数")

打印日志:

静态方法被调用,传入参数:print内容
toString:Test(name='默认名字')
Test的invoked方法被执行。参数为有参方法的参数

分析: 上面做了几件事:

  • 调用静态私有方法
  • 调用公共无参方法有返回值
  • 调用公共有参方法无返回值

如果说上面的描述太抽象,那么我们就掰碎了来念叨念叨。先看看 Method 对象的 invoke 方法源码:

 @FastNative
 public native Object invoke(Object obj, Object... args)
          throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

静态方法和成员方法的区别在于,前者通过 Class 对象即可调用,而后者需要对象才能调用,具体见示例中的 obj 的区别
私有方法和公共方法的区别在于,前者会有辅助方法检查,而排除辅助方法的检查,示例也有说明。
无参方法和有参方法的区别在于,args 这个参数前者不需要,后者按需添加一个或者多个(有参方法的获取需要提前声明参数的类型),具体参见示例。
返回值和无返回值的区别很简单,就是方法运行结果返回对象,示例中返回的是字符串,具体看示例。

  1. 构造方法的获取与使用

示例:

 //所有公共构造函数
        
 EasyLog.DEFAULT.e("constructor:${Test::class.java.constructors.easyFormat()}")
 //本类中所声明的构造函数

 EasyLog.DEFAULT.e("declared:${Test::class.java.declaredConstructors.easyFormat()}")

 //一个参数的构造函数
 val m1 = Test::class.java.getConstructor(String::class.java)
 val str1 = m1.newInstance("Vincent").toString()
 EasyLog.DEFAULT.e("m1:$str1")
 //多个参数的构造函数
 val m2 = Test::class.java.getDeclaredConstructor(Array<String>::class.java)
 val str2 = m2.newInstance(arrayOf("V","i","n","c","e","n","t")).toString()
 EasyLog.DEFAULT.e("m2:$str2")
 //无参构造函数
 val m3 = Test::class.java.getDeclaredConstructor()
 val str3 = m3.newInstance().toString()
 EasyLog.DEFAULT.e("m3:$str3")
 //无参构造函数
 val m4 =  Test::class.java.getConstructor()
 val str4 = m4.newInstance().toString()
 EasyLog.DEFAULT.e("m4:$str4")
 //无参构造函数
 val m5 = Test::class.java.newInstance()
 EasyLog.DEFAULT.e("m5:${m5.toString()}")

打印日志:

 constructor:[
     public com.haoge.sample.easyandroid.activities.Test(), 
     public com.haoge.sample.easyandroid.activities.Test(java.lang.String), 
     public transient com.haoge.sample.easyandroid.activities.Test(java.lang.String[])
 ]

declared:[
     public com.haoge.sample.easyandroid.activities.Test(), 
     public com.haoge.sample.easyandroid.activities.Test(java.lang.String), 
     public transient com.haoge.sample.easyandroid.activities.Test(java.lang.String[])
 ]

m1:Test(name='Vincent')
m2:Test(name='[V, i, n, c, e, n, t]')
m3:Test(name='默认名字')
m4:Test(name='默认名字')
m5:Test(name='默认名字')

分析:

constructorsdeclaredConstructors 一般情况下是一致的(权限修饰符此处没有单独测试) 如果构造方法接受多个参数的话,那么多个参数的类型是一致的,因此构造方法接受的是数组<参数类型>,注意不能写成 arrayListOf ,因为这是集合。
关于示例中的私有且无参的构造方法,我们演示了三种方法,前面两种都是通过调用构造方法 Constructor 的方法来获取被反射的对象,最后一种是直接通过 Class 自身的无参构造构造方法获取的。通过上述代码测试,私有构造方法不需要排除辅助方法的检查。
最后是有参构造方法,这个就很标准,通过 java 的 Class 引用来获取有参构造函数对象,参数为 Java 的 Class 对象,最后调用构造函数对象的 newInstance 方法,参数为 Class 对象的示例。说起来比较绕,看看示例就两行代码的事情!

四、反射字段访问


  1. 反射字段

直接看示例:

 val fields = Test::class.java.fields
 val declaredFields = Test::class.java.declaredFields
 EasyLog.DEFAULT.e(fields.easyFormat())
 EasyLog.DEFAULT.e(declaredFields.easyFormat())

此处为了测试,有修改测试类:

// 用于进行测试的类
class Test (private val  name:String):TestParent(name){
    constructor():this("默认名字")
    constructor(vararg name:String):this(Arrays.toString(name)){

    }

    val a = 1
    var b = true

    override fun toString(): String {
        return "Test(name='$name')"
    }

    fun invoked() {
        EasyLog.DEFAULT.e("无参数的invoked方法被执行")
    }

    fun invoked(name:String){
        EasyLog.DEFAULT.e("Test的invoked方法被执行。参数为$name")
    }

    fun onlyVararg(vararg name:String) {
        EasyLog.DEFAULT.d(name)
    }

    fun withVararg(perfix:Int, vararg name:String) {
        EasyLog.DEFAULT.d("perfix = $perfix + name = ${name.easyFormat()}")
    }



    companion object {
        @JvmStatic
        private fun print(message:String) {
            EasyLog.DEFAULT.e("静态方法被调用,传入参数:$message")
        }

        @JvmStatic
        private val staticValue = "静态文本"

        public val c = "this is c"


    }
}

打印日志:

fields:[
			public static final com.haoge.sample.easyandroid.activities.Test$Companion com.haoge.sample.easyandroid.activities.Test.Companion
	   ]
declaredFields:[
					public static final com.haoge.sample.easyandroid.activities.Test$Companion com.haoge.sample.easyandroid.activities.Test.Companion, 
					private static final java.lang.String com.haoge.sample.easyandroid.activities.Test.c, 
					private static final java.lang.String com.haoge.sample.easyandroid.activities.Test.staticValue, 
					private final java.lang.String com.haoge.sample.easyandroid.activities.Test.name, 
					private boolean com.haoge.sample.easyandroid.activities.Test.b, 
					private final int com.haoge.sample.easyandroid.activities.Test.a
]

分析:

通过上述代码以及日志可以得出:Test 的伴生类其实也是一个公开的静态字段,而其它普通字段,默认的权限修饰符是 private , val 字段会有 final 修饰符,而 var 作为可变变量,则没有。(我看到这里的时候对 var 和 val 有种豁然开朗的感觉) 通过对 fields 和 declaredFields 的对比,前者是公共字段的集合(并不包括父类),后者是本类所有声明的变量。

  1. 访问字段:

示例: //测试前请手动将字段 c 修饰符修改为 public

 val t = Test::class.java
 val obj = t.newInstance()
 //公共字段 a
 val c = t.getField("c")
 EasyLog.DEFAULT.e("c:${c.get(obj)}")
 //私有静态字段 staticValue
 val d = t.getDeclaredField("staticValue")
 //排除辅助检查
 d.isAccessible = true
 EasyLog.DEFAULT.e("staticValue:${d.get(obj)}")
 //私有字段
 val a = t.getDeclaredField("a")
 a.isAccessible = true
 EasyLog.DEFAULT.e("a:${a.get(obj)}")

打印日志:

c:this is c
staticValue:静态文本
a:1

分析:

在测试过程中发现,伴生类静态字段不能设置 public 权限修饰符,const 字段不能被反射。(需要自行写代码测试,建议看看 data class 和 object 有什么区别) 静态字段和非静态字段在反射过程中并没有区别,但是私有字段和公共字段的反射过程是有区别的,前者还是需要排除辅助检查

  1. 操作字段:

示例:

//需手动修改 a 、 c 为只读字段,staticValue 为可变字段

 val t = Test::class.java
 val obj = t.newInstance()
  try {//公共字段 a
      val c = t.getField("c")
      c.set(obj,"this is not c")
      EasyLog.DEFAULT.e("c:${c.get(obj)}")
  } catch (e: Exception) {
      EasyLog.DEFAULT.e("final 修饰的字段不可被反射修改${e.message}")
  }
  //私有静态字段 staticValue
  val d = t.getDeclaredField("staticValue")
  //排除辅助检查
  d.isAccessible = true
  d.set(obj,"this is d")
  EasyLog.DEFAULT.e("staticValue:${d.get(obj)}")
  try {//私有字段
      val a = t.getDeclaredField("a")
      a.isAccessible = true
      a.set(obj,100)
      EasyLog.DEFAULT.e("a:${a.get(obj)}")
  } catch (e: Exception) {
      EasyLog.DEFAULT.e("final 修饰的字段不可被反射修改${e.message}")
  }

打印日志:

final 修饰的字段不可被反射修改field is marked 'final'
staticValue:this is d
a:100

分析: 在操作字段的方法很简单,不过我们在操作两个只读字段的时候添加了 try catch ,并且第一个执行了catch 方法而第二个没有执行,为什么呢?因为前者是静态变量,而且我们获取到字段的时候,在设置字段之前已经实例化了 Test 对象,因此静态只读字段已经被赋值,此时修改发生了异常—— field is marked 'final' 。而后者是我们操作字段之前还没有被赋值,因此我们在后面修改的时候并没有发生异常。此处为我的猜测,建议不要修改只读字段。参考Java反射机制可以动态修改实例中final修饰的成员变量吗?

五、总结


在动手写本文之前,我先通过 Kotlin 中文网进行了学习,但是在反射的章节中,文章并没有讲解反射相关的知识,而是在讲解引用:类引用和可调用类引用。于是本文的所有内容都是基于 java 的反射知识在 Kotlin 的运用而已。

最后谢谢阅读,如有勘误,请斧正!