algorithm - 递归过滤和映射属性列表
问题描述
我正在使用 Kotlin 反射来检查具有特定注释的属性是否为空。
给定以下示例:
data class DataClass(
@SomeRandomAnnotation
val otherAnnotated: String?,
val inner: InnerClass
)
data class AnotherDataClass(
@SomeRandomAnnotation
val annotatedProperty: String?,
val dataClass: DataClass
) {
fun checkCreditAnalysisConstrain() {
print(checkConstrain(this))
}
}
以及检查它的功能:
fun checkConstrain(parentClass: Any): List<String> {
val filter = parentClass::class.memberProperties.filter {
if (memberIsDataClass(it)) checkConstrain(getMemberPropertyInstance(parentClass, it))
hasAnnotation(it) && propertyIsNull(it, parentClass)
}
return filter.map { formatResult(parentClass, it) }
}
这个想法是该函数将遍历我的类的属性,检查它们是否具有注释并检查值是否为空。如果该属性是一个数据类,则代码会递归地评估子项的属性。
之后,我映射结果,将 KProperty 转换为人类可读的简单字符串,其中包含类名和属性名。
问题是上面的代码没有按预期工作。返回的属性只是来自第一级类的属性。
如果我不做过滤器,而是运行一个 forEach 并打印结果,我会得到预期的属性。所以我很确定它与过滤器内的重复出现有关。
您是否看到任何以更实用的方式执行此操作的方法?我只是担心我不需要“临时”列表并将值添加到列表中并在之后重置它。
解决方案
您的函数递归调用自身,但对该递归调用的返回列表不执行任何操作。这就是为什么您只能获得顶级课程的结果。
filter
另外,在我看来,您不应该依赖通话中发生的副作用。它可能有效,但该函数的文档并不能保证集合中的每个项目都会调用一次。所以应该有一个单独的for循环来进行递归调用,结果应该添加到现有结果中。
fun checkConstrain(parent: Any): List<String> {
val memberProperties = parent::class.memberProperties
var result = memberProperties
.filter { hasAnnotation(it) && propertyIsNull(it, parent) }
.map { formatResult(parent, it) }
memberProperties.filter { memberIsDataClass(it) }
.mapNotNull { getMemberPropertyInstance(parent, it) }
.forEach { result += checkConstrain(it) }
return result
}
您没有为您使用的几个函数提供代码。这是我为他们使用的:
val KProperty<*>.returnTypeClass get() = this.returnType.classifier as? KClass<*>
fun <T> memberIsDataClass(member: KProperty<T>) = member.returnTypeClass?.isData == true
fun <T> getMemberPropertyInstance(parent: Any, property: KProperty<T>) = property.getter.call(parent)
fun <T> hasAnnotation(property: KProperty<T>) = property.annotations.firstOrNull { it.annotationClass == SomeRandomAnnotation::class } != null
fun <T> propertyIsNull(property: KProperty<T>, parent: Any) = getMemberPropertyInstance(parent, property) == null
fun formatResult(parent: Any, property: KProperty<*>) = "$parent's property(${property.name}) is annotated with SomeRandomAnnotation and is null."