Kotlin 是否有任何方法可以在不使用反射的情况下迭代数据类的所有字段?
我知道反射的另一种选择是使用javassist,但是使用javassist有点复杂。而且,由于lambda或koltin中的其他一些特性,javassist有时无法正常工作。因此,有没有其他方法可以在不使用反射的情况下迭代数据类的所有字段。有两种方法。第一个相对简单,本质上就是注释中提到的:假设您知道有多少字段,您可以将其解压缩并放入列表中,然后迭代这些字段。或者直接使用它们:Kotlin 是否有任何方法可以在不使用反射的情况下迭代数据类的所有字段?,kotlin,Kotlin,我知道反射的另一种选择是使用javassist,但是使用javassist有点复杂。而且,由于lambda或koltin中的其他一些特性,javassist有时无法正常工作。因此,有没有其他方法可以在不使用反射的情况下迭代数据类的所有字段。有两种方法。第一个相对简单,本质上就是注释中提到的:假设您知道有多少字段,您可以将其解压缩并放入列表中,然后迭代这些字段。或者直接使用它们: data class Test(val x: String, val y: String) { fun get
data class Test(val x: String, val y: String) {
fun getData() : List<Any> = listOf(x, y)
}
尽管像这样迭代是一个稍微额外的步骤,但这至少是一种相对容易地解压数据类的方法
如果您不知道有多少字段(或者您不想重构),您有两个选择: 第一种是使用反射。但正如你提到的,你不想要这个 这就留下了第二个更复杂的预处理选项:注释注意,这只适用于您控制的数据类——除此之外,您还需要使用库/框架编码器的反射或实现 注释可以用于多种用途。其中之一是元数据,但也是代码生成。这是一个有点复杂的替代方案,需要额外的模块才能获得正确的编译顺序。如果没有按照正确的顺序编译,您将得到未经处理的注释,这有点违背了目的 我还创建了一个可以与Gradle一起使用的版本,但这是本文的结尾,这是一个自己实现它的快捷方式 请注意,我只在纯Kotlin项目中测试过这一点——我个人在Java和Kotlin之间的注释方面遇到了问题(尽管这是在Lombok中),因此我不保证如果从Java调用,这将在编译时起作用。还要注意,这很复杂,但可以避免运行时反射
解释 这里的主要问题是内存问题。这将在每次调用该方法时创建一个新的列表,这使它非常类似于 对10000次迭代进行的本地测试也表明,执行我的方法的一致性一般为200毫秒,而反射大约为600毫秒。然而,对于一次迭代,mine使用约20毫秒,而as反射使用400到500毫秒。在一次运行中,反射耗时1500(!)毫秒,而我的方法耗时18毫秒 另见。这似乎也会影响科特林。 虽然每次调用新列表时都会对内存产生明显影响,但它也会被收集起来,所以不会有太大问题 作为参考,用于基准测试的代码(这将在文章的其余部分之后有意义): 此外,还需要添加存根生成。它抱怨编译时未使用它,但如果没有它,生成器似乎会为我崩溃:
kapt {
generateStubs = true
}
现在已经准备好了,为解包程序创建一个新模块。如果尚未添加Kotlin插件,请添加该插件。此项目中不需要annotation processor Gradle插件。这只是消费者需要的。但是,您确实需要:
这是为了简化代码生成本身的各个方面,这是这里的重要部分
现在,创建注释:
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class AutoUnpack
这就是你所需要的。保留设置为source,因为它在运行时没有值,并且只针对编译时
接下来是处理器本身。这有点复杂,请耐心听我说。作为参考,它使用javax.*
包进行注释处理。Android注意:假设您可以在compileOnly
范围内插入Java模块,而不受Android SDK的限制,这可能会起作用。正如我前面提到的,这主要是针对纯Kotlin;安卓可能会工作,但我还没有测试过
无论如何,发电机:
因为我无法找到一种方法在不涉及其余部分的情况下将方法生成到类中(而且根据,这是不可能的),所以我将使用扩展函数生成方法
您需要一个类UnpackCodeGenerator:AbstractProcessor()
。在这里,您首先需要两行样板文件:
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf(AutoUnpack::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
所有相关的行都有注释解释使用,以防您不熟悉它的用途
最后,为了让处理器进行处理,您需要注册它。在生成器的模块中,在main/resources/META-INF/services
下添加一个名为javax.annotation.processing.Processor
的文件。你在里面写着:
com.package.of.unpackage代码生成器
从这里开始,您需要使用compileOnly
和kapt
链接它。如果将其作为模块添加到项目中,则可以执行以下操作:
kapt project(":ClassUnpacker")
compileOnly project(":ClassUnpacker")
替代源设置:
就像我前面提到的,为了方便,我把它塞进了一个罐子里。它与SO使用的许可证相同(CC-BY-SA3.0),并且它包含与答案中完全相同的代码(尽管编译成单个项目)
如果您想使用这个,只需添加Jitpack回购:
repositories {
// Other repos here
maven { url 'https://jitpack.io' }
}
并将其连接到:
kapt 'com.github.LunarWatcher:KClassUnpacker:v1.0.1'
compileOnly "com.github.LunarWatcher:KClassUnpacker:v1.0.1"
请注意,此处的版本可能不是最新的:可以使用最新的版本列表。文章中的代码仍然旨在反映回购协议,但版本并不重要,不能每次都进行编辑
用法
无论最终使用哪种方式获取注释,使用都相对简单:
@AutoUnpack data class ExampleDataClass(val x: String, val y: Int, var m: Boolean)
fun main(a: Array<String>) {
val cls = ExampleDataClass("example", 42, false)
for(field in cls) {
println(field)
}
}
现在您有了一种迭代字段的无反射方式
请注意,IntelliJ已经部分完成了本地测试,但IntelliJ似乎不喜欢我-我有过各种失败的版本,其中来自命令行的gradlew clean&&gradlew build
运行异常良好。我不确定这是否是一个局部问题,或者这是否是一个普遍问题,但你可能会有一些问题
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
// Find elements with the annotation
val annotatedElements = roundEnv.getElementsAnnotatedWith(AutoUnpack::class.java)
if(annotatedElements.isEmpty()) {
// Self-explanatory
return false;
}
// Iterate the elements
annotatedElements.forEach { element ->
// Grab the name and package
val name = element.simpleName.toString()
val pkg = processingEnv.elementUtils.getPackageOf(element).toString()
// Then generate the class
generateClass(name,
if (pkg == "unnamed package") "" else pkg, // This is a patch for an issue where classes in the root
// package return package as "unnamed package" rather than empty,
// which breaks syntax because "package unnamed package" isn't legal.
element)
}
// Return true for success
return true;
}
private fun generateClass(className: String, pkg: String, element: Element){
val elements = element.enclosedElements
val classVariables = elements
.filter {
val name = if (it.simpleName.contains("\$delegate"))
it.simpleName.toString().substring(0, it.simpleName.indexOf("$"))
else it.simpleName.toString()
it.kind == ElementKind.FIELD // Find fields
&& Modifier.STATIC !in it.modifiers // that aren't static (thanks to sebaslogen for issue #1: https://github.com/LunarWatcher/KClassUnpacker/issues/1)
// Additionally, we have to ignore private fields. Extension functions can't access these, and accessing
// them is a bad idea anyway. Kotlin lets you expose get without exposing set. If you, by default, don't
// allow access to the getter, there's a high chance exposing it is a bad idea.
&& elements.any { getter -> getter.kind == ElementKind.METHOD // find methods
&& getter.simpleName.toString() ==
"get${name[0].toUpperCase().toString() + (if (name.length > 1) name.substring(1) else "")}" // that matches the getter name (by the standard convention)
&& Modifier.PUBLIC in getter.modifiers // that are marked public
}
} // Grab the variables
.map {
// Map the name now. Also supports later filtering
if (it.simpleName.endsWith("\$delegate")) {
// Support by lazy
it.simpleName.subSequence(0, it.simpleName.indexOf("$"))
} else it.simpleName
}
if (classVariables.isEmpty()) return; // Self-explanatory
val file = FileSpec.builder(pkg, className)
.addFunction(FunSpec.builder("iterator") // For automatic unpacking in a for loop
.receiver(element.asType().asTypeName().copy()) // Add it as an extension function of the class
.addStatement("return listOf(${classVariables.joinToString(", ")}).iterator()") // add the return statement. Create a list, push an iterator.
.addModifiers(KModifier.PUBLIC, KModifier.OPERATOR) // This needs to be public. Because it's an iterator, the function also needs the `operator` keyword
.build()
).build()
// Grab the generate directory.
val genDir = processingEnv.options["kapt.kotlin.generated"]!!
// Then write the file.
file.writeTo(File(genDir, "$pkg/${element.simpleName.replace("\\.kt".toRegex(), "")}Generated.kt"))
}
kapt project(":ClassUnpacker")
compileOnly project(":ClassUnpacker")
repositories {
// Other repos here
maven { url 'https://jitpack.io' }
}
kapt 'com.github.LunarWatcher:KClassUnpacker:v1.0.1'
compileOnly "com.github.LunarWatcher:KClassUnpacker:v1.0.1"
@AutoUnpack data class ExampleDataClass(val x: String, val y: Int, var m: Boolean)
fun main(a: Array<String>) {
val cls = ExampleDataClass("example", 42, false)
for(field in cls) {
println(field)
}
}
data class Memento(
val testType: TestTypeData,
val notes: String,
val examinationTime: MillisSinceEpoch?,
val administeredBy: String,
val signature: SignatureViewHolder.SignatureData,
val signerName: String,
val signerRole: SignerRole
) : Serializable
val iterateThroughAllMyFields: Memento = someValue
Memento(
testType = iterateThroughAllMyFields.testType.also { testType ->
// do something with testType
},
notes = iterateThroughAllMyFields.notes.also { notes ->
// do something with notes
},
examinationTime = iterateThroughAllMyFields.examinationTime.also { examinationTime ->
// do something with examinationTime
},
administeredBy = iterateThroughAllMyFields.administeredBy.also { administeredBy ->
// do something with administeredBy
},
signature = iterateThroughAllMyFields.signature.also { signature ->
// do something with signature
},
signerName = iterateThroughAllMyFields.signerName.also { signerName ->
// do something with signerName
},
signerRole = iterateThroughAllMyFields.signerRole.also { signerRole ->
// do something with signerRole
}
)