首页 > 解决方案 > 递归过滤和映射属性列表

问题描述

我正在使用 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 并打印结果,我会得到预期的属性。所以我很确定它与过滤器内的重复出现有关。

您是否看到任何以更实用的方式执行此操作的方法?我只是担心我不需要“临时”列表并将值添加到列表中并在之后重置它。

标签: algorithmkotlinreflection

解决方案


您的函数递归调用自身,但对该递归调用的返回列表不执行任何操作。这就是为什么您只能获得顶级课程的结果。

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."

推荐阅读