Kotlin - 如何避免在密封类的 when() 语句中转换第二个变量

Dav*_*idT 3 kotlin

我有一个代表字段类型的密封类:

sealed class FieldDef {
    object StringField: FieldDef()
    class ListField(val element: FieldDef): FieldDef()
    class MapField(val children: Map<String, FieldDef>): FieldDef()

    // ... more field types
}
Run Code Online (Sandbox Code Playgroud)

我有一个处理一对字段定义的函数。

    fun processSameTypes(fd1: FieldDef, fd2: FieldDef) {
        if (fd1::class == fd2::class) {
            when (fd1) {
                is MapField -> processMaps(fd1, (fd2 as MapField))
                is ListField -> processLists(fd1, (fd2 as ListField))
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

编写的代码是正确的 - 鉴于逻辑, fd2 变量的转换将始终成功。

但我觉得应该有一种方法可以在不使用强制转换的情况下表达这一点,但是如果我尝试删除强制转换,则会收到错误:

Kotlin: Type mismatch: inferred type is FieldDef but FieldDef.ListField was expected
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来处理这个问题,以便我可以移除演员(演员不是必需的)。

如果没有更好的方法来解决这个问题,您能否解释一下为什么编译器无法推断变量的正确类型fd2。我的逻辑是,由于when受外部保护if并且FieldDef是一个密封类,因此编译器应该能够推断 的类型fd2

Kotlin 版本:1.5.30 (JVM)

编辑:结论

我认为可以公平地说,传递类对象是 Java 的惯用方式(也通过其继承的 Kotlin),特别是它是在运行时表示类型的方式。因此,我希望将来能够看到 Kotlin 编译器的支持。

也就是说,它今天不支持它,所以我同意@TreffnonX - 只是吸收它并投入演员阵容是最好的解决方案 - 因此我不会更改我的代码。

不过,我确实要求一种不使用演员表来解决问题的方法,@Sweeper 提供了一个 - 所以我会接受这个答案。

Swe*_*per 5

不要给 提供参数when,以便您可以检查多个事物的类型。

// Now you can remove "if (fd1::class == fd2::class)" too
when {
    fd1 is FieldDef.MapField && fd2 is FieldDef.MapField -> 
        processMaps(fd1, fd2)
    fd1 is FieldDef.ListField && fd2 is FieldDef.ListField -> 
        processLists(fd1, fd2)
}
Run Code Online (Sandbox Code Playgroud)

或者,创建您自己的内联函数来检查两者:

inline fun <reified TTarget> checkBothTypes(a: Any, b: Any, block: (TTarget, TTarget) -> Unit) {
    if (a is TTarget && b is TTarget) {
        block(a, b)
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种编写方式是允许从块返回结果:

inline fun <reified TTarget, TResult> checkBothTypes(
    type: KClass<TTarget>, a: Any, b: Any, block: (TTarget, TTarget) -> TResult
): TResult? {
    if (a is TTarget && b is TTarget) {
        return block(a, b)
    }
    return null
}
Run Code Online (Sandbox Code Playgroud)

用法:

checkBothTypes<FieldDef.ListField>(fd1, fd2) { l1, l2 ->
    processLists(l1, l2) // or use a function reference
    // ...
}
checkBothTypes<FieldDef.MapField>(fd1, fd2) { m1, m2 ->
    processMaps(m1, m2) // or use a function reference
    // ...
}
Run Code Online (Sandbox Code Playgroud)

如果您想从所有检查中获取结果,请使用?:将调用链接在一起,创建单个表达式。

请注意,与使用时不同when(...),这些方法无法检测到 的子类的缺失检查FieldDef