kavaref-extension
这是 KavaRef 相关功能的扩展依赖。
配置依赖
你可以使用如下方式将此模块添加到你的项目中。
我们推荐你优先参考 kavaref-bom 使用 BOM 统一管理版本。
Version Catalog (推荐)
在你的项目 gradle/libs.versions.toml 中添加依赖。
[versions]
kavaref-extension = "<version>"
[libraries]
kavaref-extension = { module = "com.highcapable.kavaref:kavaref-extension", version.ref = "kavaref-extension" }在你的项目 build.gradle.kts 中配置依赖。
implementation(libs.kavaref.extension)请将 <version> 修改为此文档顶部显示的版本。
传统方式
在你的项目 build.gradle.kts 中配置依赖。
implementation("com.highcapable.kavaref:kavaref-extension:<version>")请将 <version> 修改为此文档顶部显示的版本。
功能介绍
你可以 点击这里 查看 KDoc。
Class 扩展
KavaRef 提供了一些扩展,在处理 Class 对象时会更加方便。
KavaRef 对 Class 的扩展同样添加了 KClass 扩展,作用是调用 KClass.java,写法上比直接使用 Some::class.java 更加简洁。
创建 Class 对象
例如我们需要使用字符串类名创建一个 Class 对象。
示例如下
val myClass = "com.example.MyClass".toClass()
// 你可以使用带有 OrNull 后缀的方法在找不到 Class 时返回 null 而不是抛出异常
val myClassOrNull = "com.example.MyClass".toClassOrNull()这些方法统一使用 ClassLoaderProvider 来获取默认的 ClassLoader,你可以设置默认的 ClassLoader 以影响全局功能。
示例如下
ClassLoaderProvider.classLoader = MyCustomClassLoader()你也可以手动向 toClass 方法传入一个 ClassLoader 参数来指定使用哪个 ClassLoader。
如果没有传入 ClassLoader 且未设置 ClassLoaderProvider.classLoader,将会使用装载 KavaRef 的 ClassLoader。
小提示
toClass 方法默认不会初始化 Class (不会执行其中的静态代码块),如果你需要初始化,请设置 initialize = true。
Class 对象引用
在 Kotlin 中引用 Java Class 需要写很长的声明,例如 MyClass::class.java,此时你可以用以下方式来简化。
示例如下
val myClass = classOf<MyClass>()你可以使用 isSubclassOf 方法来判断一个 Class 是否是另一个 Class 的子类。
示例如下
val isSubclass = MyClass::class isSubclassOf MySuperClass::class
// 当然,它也有一个对应的反义判断方法
val isNotSubclass = MyClass::class isNotSubclassOf MySuperClass::class你还可以使用 hasSuperclass 和 hasInterfaces 方法来判断一个 Class 是否有超类或接口。
特别注意
classOf 方法传入的 Class 默认会进行 Java 包装类的拆箱操作,无论你传入的是类似 kotlin.Boolean 还是 java.lang.Boolean (参考下方的 Java 包装类扩展), 如果你需要避免传入的 Class 被拆箱变为原始类型,你需要明确设置 primitiveType = false 参数。
创建新的实例
KavaRef 为 Class 提供了一个方法来方便地创建一个新的实例,你不需要考虑构造参数的类型,你只需要传入对应的参数即可立即创建一个新的实例。
示例如下
val myClass = MyClass::class.createInstance("Hello", 123)
// 你也可以使用带有 OrNull 后缀的方法在创建失败时返回 null 而不是抛出异常
val myClassOrNull = MyClass::class.createInstanceOrNull("Hello", 123)
// createInstance 方法默认仅过滤公开的构造方法,如果你需要调用非公有构造方法,请设置 isPublic = false
val myClassWithPrivateConstructor = MyClass::class.createInstance("Private!", isPublic = false)
// 如果你想指定创建实例的类型使用另一个类型,可以使用以下方法
val mySuperClass = MyClass::class.createInstanceAsType<MySuperClass>("Hello", 123)
// 同样地,你也可以使用带有 OrNull 后缀的方法在创建失败时返回 null 而不是抛出异常
val mySuperClassOrNull = MyClass::class.createInstanceAsTypeOrNull<MySuperClass>("Hello", 123)如果有多个构造方法都可以接受传入的参数,KavaRef 会参考 Java 的重载规则,优先选择无需拆箱即可匹配的构造方法,然后选择其中参数类型最具体的一个。
示例如下
// 假设 MyClass 同时存在 MyClass(Object) 与 MyClass(String) 两个构造方法
// 这里将会使用 MyClass(String)
val myClass = MyClass::class.createInstance("Hello")小提示
createInstance 方法在成功匹配一次后,会将结果进行缓存防止重复反射造成的性能损耗,它是线程安全的,你可以放心在任何标准场景下使用。
缓存不会阻止 Class 及其 ClassLoader 被回收,在插件化等需要动态装载和卸载 ClassLoader 的场景下也可以放心使用。
特别注意
当你传入带有 null 的参数时,KavaRef 会尝试将其作为可匹配到条件的一部分 (模糊条件),准确性可能会下降。
createInstance 方法不允许所有参数均为 null 的情况 (条件完全模糊),会直接抛出异常,因为这种情况无法确定要创建哪个实例。
如果无法确定唯一一个参数类型最具体的构造方法 (例如传入的 null 使多个构造方法无法区分),也会直接抛出异常,而不是随意选择其中一个。
Class 修饰符
KavaRef 也对 Modifier 进行了扩展,你可以直接使用 Class.isPublic 等方法来判断一个 Class 的修饰符。
VariousClass
KavaRef 提供了 VariousClass 类来装载不确定完整类名的 Class 对象,并返回成功匹配到的第一个。
此功能通常可用于 Android 应用中那些被 R8 混淆后的类名。
示例如下
// 假设在 A 版本中,这个类为 com.example.a,
// 在 B 版本中,这个类为 com.example.b
val myClass = VariousClass("com.example.a", "com.example.b").load()
// 你也可以使用后缀名为 OrNull 的方法在找不到 Class 时返回 null 而不是抛出异常
val myClassOrNull = VariousClass("com.example.a", "com.example.b").loadOrNull()VariousClass 装载 Class 时使用的 ClassLoader 与 toClass 方法相同。
延迟装载 Class 对象
KavaRef 提供了 LazyClass 类来延迟装载 Class 对象。
你可以在需要时再装载 Class,而不是在创建时就立即装载,这可以解决一些需要运行时或运行到特定条件下才需要装载的 Class。
示例如下
// 定义一个不可为空延迟装载的 Class 并托管给 myClass
val myClass by lazyClass("com.example.MyClass")
// 定义一个可为空延迟装载的 Class 并托管给 myClassOrNull
val myClassOrNull by lazyClassOrNull("com.example.MyClass")
// 它亦可支持传入 VariousClass
val otherClassOrNull by lazyClassOrNull(VariousClass("com.example.a", "com.example.b"))
// 在需要时调用即装载
myClass.resolve()
myClassOrNull?.resolve()
otherClassOrNull?.resolve()ClassLoader 扩展
KavaRef 还为 ClassLoader 提供了一些实用的扩展方法。
示例如下
// 假设这就是你的 ClassLoader
val classLoader: ClassLoader
// 装载一个 Class,在装载失败时返回 null
val myClassOrNull = classLoader.loadClassOrNull("com.example.MyClass")
// 判断这个 Class 是否存在于当前 ClassLoader 中
val isClassExists = classLoader.hasClass("com.example.MyClass")数组 Class 扩展
在 Java 中,数组的 Class 对象是一个特殊的 Class 对象,通常,我们创建它的方式如下。
例如创建一个 java.lang.String[] 的 Class 对象。
示例如下
val arrayClass = java.lang.reflect.Array.newInstance(String::class.java, 0).javaClass这样写起来非常长,而且不方便维护,所以 KavaRef 提供了一个方法来简化这个过程。
现在,创建 java.lang.String[] 的 Class 对象可以这样写。
示例如下
val arrayClass = ArrayClass(String::class)Member 扩展
KavaRef 提供了一些扩展方法来简化对 Member 的操作。
你可以在任何 Member 对象上使用 makeAccessible 方法来设置其可访问性。
如果 Member 是 AccessibleObject 类型即可生效。
示例如下
// 假设这个是你当前的 Member 对象
val method: Method
// 设置方法可访问,返回是否成功
val isSuccess = method.makeAccessible()同样地,KavaRef 也对 Modifier 进行了扩展,你可以直接使用 Member.isPublic 等方法来判断一个 Member 的修饰符。
Type 扩展
在 Java 中操作类型或泛型类型时,通常需要使用 Type 接口及其子接口来处理。
KavaRef 提供了一些扩展方法来简化对 Type 的操作。
例如,你可以将一个符合要求的 Type 转换为 Class 对象。
示例如下
val type: Type
val clazz = type.toClass()
// 你也可以使用后缀名为 OrNull 的方法在转换失败时返回 null 而不是抛出异常
val clazzOrNull = type.toClassOrNull()toClass 同样支持泛型数组 (GenericArrayType),转换时会保留数组的维度。
示例如下
// 假设这是 List<String>[] 的 Type
val genericArrayType: GenericArrayType
// 将会得到 java.util.List[]
val arrayClass = genericArrayType.toClass()类型变量 (TypeVariable) 和通配符类型 (WildcardType) 没有确定的 Class,转换时将会抛出异常。
你也可以将符合要求的 Type 转换为 ParameterizedType 对象。
示例如下
val type: Type
val parameterizedType = type.asParameterizedType()
// 你也可以使用后缀名为 OrNull 的方法在转换失败时返回 null 而不是抛出异常
val parameterizedTypeOrNull = type.asParameterizedTypeOrNull()你还可以使用以下方式获取超类中的泛型参数数组,这在一些超类与子类的封装操作中会经常用到。
示例如下
val myClass: Class<*>
// 获取 myClass 的超类的泛型参数数组,获取失败或无法获取时将返回空数组
val arguments = myClass.genericSuperclassTypeArguments()类型引用扩展
在 Java 中,泛型在编译后会被擦除,运行时无法直接从一个对象上得知它的泛型参数。
KavaRef 提供了 TypeRef 类,它通过匿名子类的泛型签名来保留完整的类型信息,核心功能参考于 Gson 的 TypeToken。
它的使用方法非常简单,你可以像下面这样使用它。
示例如下
val listStringType = typeRef<List<String>>()
// 获取存储的类型,将会是 List<? extends String>
val type = listStringType.type
// 获取其原始类型,将会是 List
val rawType = listStringType.rawTyperawType 同样支持泛型数组,并会保留数组的维度。
示例如下
// 将会是 java.util.List[]
val arrayRawType = typeRef<Array<List<String>>>().rawType
// 多维数组同样适用,将会是 java.util.List[][]
val nestedArrayRawType = typeRef<Array<Array<List<String>>>>().rawType在使用 Gson 等需要传入 Type 的场景中,你可以为此实现一个带有 reified 泛型的扩展方法。
示例如下
val gson = Gson()
inline fun <reified T> T.toJson(): String = gson.toJson(this, typeRef<T>().type)
// 使用方法
val json = listOf("KavaRef", "is", "awesome").toJson()注意
TypeRef 得到的是 Java 的 Type,它不包含 Kotlin 的可空性,所以 typeRef<String?>() 与 typeRef<String>() 是相等的,List<String?>? 也同理。
如果你需要区分可空性,请使用 Kotlin 的 typeOf<T>() 获取 KType。
特别注意
TypeRef 只能被直接继承,例如 object : TypeRef<List<String>>() {},继承一个 TypeRef 的子类后,获取 type 或 rawType 时将会抛出异常。
在没有 reified 的泛型方法中创建 TypeRef 时,泛型参数不会被替换为调用时传入的类型。
示例如下
fun <T> createTypeRef() = object : TypeRef<T>() {}
val typeRef = createTypeRef<String>()
// 得到的是类型变量 T,而不是 String
val type = typeRef.type
// T 没有确定的 Class,这里将会抛出异常
val rawType = typeRef.rawTypeList<T> 这类本身有原始类的类型不受影响,rawType 仍然是 List,但 T 和 Array<T> 的 rawType 都无法获取。
所以,请尽量在带有 reified 泛型的方法中使用 typeRef<T>()。
Java 包装类扩展
在 Kotlin 中直接使用 Boolean::class、Byte::class 等方式获取到的是 Java 的原始类型 boolean、byte 而不是它们的包装类。
如果你需要获取 Java 的包装类,你需要使用完整的 java.lang.Boolean::class、java.lang.Byte::class 等方式或使用 Boolean::class.javaObjectType、Byte::class.javaObjectType。
所以,KavaRef 提供了一些类型别名来处理 Java 的包装类,现在你只需要在这些类型加上 J 前缀即可,例如 JBoolean::class, 它等价于 java.lang.Boolean::class,部分类型需要填写全称,例如 JInteger::class。