Kotlin - 类型不匹配:推断类型是 Any?但布尔值是预期的

Posted

技术标签:

【中文标题】Kotlin - 类型不匹配:推断类型是 Any?但布尔值是预期的【英文标题】:Kotlin - Type mismatch: inferred type is Any? but Boolean was expected 【发布时间】:2021-08-16 20:20:46 【问题描述】:

我正在尝试使用 Kotlin。来自 Python 的背景确实让我很难掌握 Kotlin 语法的诀窍。我正在尝试做一个简单的字典(可变映射)操作。但是,它给了我例外。

This is what I tried. Kotlin compiler

添加代码sn-p以供参考。

fun main() 
    val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
    val packageName = "amazon"
    val currentTime = 23454321234
    if(openActivityMap.containsKey(packageName)) 
        if(openActivityMap[packageName]?.get("isAlreadyApplied"))
            if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600)
                openActivityMap[packageName]?.put("isAlreadyApplied", false)
            
        
        else
            openActivityMap[packageName]?.put("isAlreadyApplied", false)
        
    

【问题讨论】:

请注意,异构映射在 Kotlin 中非常少见。我猜你的主地图的值不应该是地图本身,而是具有正确类型属性的类的实例(如isAlreadyAppliedlastAppliedAt)。您为这些人使用地图有什么特别的原因吗? 【参考方案1】:

我参加聚会有点晚了,但我想在这里指出另一个解决方案。

正如我在 OP 中评论的那样,具有像这样的固定字符串键的异构映射通常用 Kotlin 中的类更好地表达。例如,在您的情况下,主地图值的类可能如下:

data class PackageInfo(
    var isAlreadyApplied: Boolean,
    var lastAppliedAt: Long,
)

(如果需要,您显然可以添加更多属性)

这将为您节省所有对最终值的强制转换。

我想说的另一点是,如果您仍然访问某个键的值,则无需使用containsKey 预先检查该键是否存在。对于不与任何值关联的键,映射返回 null(这就是为什么您需要在获取值后检查是否为 null)。

编译器看不到containsKey 与后续get[] 访问之间的关联。但是,如果您只是先获取值然后检查是否为空,那么理解空检查就足够聪明了。

这始终适用,除非您想区分不在映射中的键和在映射中但关联空值的键(这非常罕见)。

总而言之,我会这样写:

fun main() 
    val openActivityMap = mutableMapOf<String, PackageInfo>()
    val packageName = "amazon"
    val currentTime = 23454321234
    val packageInfo = openActivityMap[packageName]
    if (packageInfo != null)  // the key was found and the value is smart cast to non-null in the next block
        if (packageInfo.isAlreadyApplied) 
            if ((packageInfo.lastAppliedAt - currentTime) > 3600) 
                packageInfo.isAlreadyApplied = false
            
         else 
            packageInfo.isAlreadyApplied = false
        
    


data class PackageInfo(
    var isAlreadyApplied: Boolean,
    var lastAppliedAt: Long,
)

【讨论】:

并不是说编译器不聪明——它知道即使containsKey 在一行返回true,当get 在下一行执行时,也不能保证键仍在地图中(因为它可能已被例如在另一个线程上运行的代码修改)。这就像一个可以为空的 var 基本上,你必须以同样的方式对待它 - 做一个 get 和一个空检查会将值分配给一个不能改变的临时变量,所以使用它是安全的 @cactustictacs 就像一个可以为空的变量 - 这不太正确,所有变量都是 main 函数的本地变量。如果它是一个局部变量,一个可为空的var 将是正确的智能转换。编译器根本没有任何工具可用于跨越 2 个方法调用的合约(containsKey 的结果不会影响其对get 的评估)。话虽如此,即使对于Map,这样做也将非常复杂(几乎不可能),因为只读并不意味着不可变,因此甚至需要知道实例来自哪里(不仅仅是参考变量) 我真的很喜欢这种方法。干净。 @cactustictacs 是的,我想我们有点同意什么是可能的。你是对的,这不仅仅是编译器本身的错,因为即使有疯狂的逃逸分析,也无法通过现有的合约系统来改进编译器以理解这一点。我所说的“编译器不够聪明”的意思是关于合约以及它们可以或不能强制执行的内容更一般(但我认为合约是编译器的一部分),我猜“编译器不能" 可能更合适。 @Joffrey 是的,这很酷,一开始我不确定你是从哪里来的,所以我只想明确指出这是有原因的,主要是为了帮助 OP!如果您收到有关无法智能转换的警告,通常是有原因的 - 最好抓住一个临时变量,而不是在编译器警告某些内容可能为空时开始抛出 !!。我们完全同意,“它不够聪明”是描述它的好方法,我只是想清楚这在这种情况下意味着什么【参考方案2】:

我建议先编写测试并以较小的增量工作,但这应该可以解决您的编译问题:

fun main() 
    val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
    val packageName = "amazon"
    val currentTime = 23454321234
    if(openActivityMap.containsKey(packageName)) 
        if(openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean)
            if((openActivityMap[packageName]?.get("lastAppliedAt") as Long - currentTime) > 3600)
                openActivityMap[packageName]?.put("isAlreadyApplied", false)
            
        
        else 
            openActivityMap[packageName]?.put("isAlreadyApplied", false)
        
    

编辑:通常我更喜欢避免可空变量和可变对象,但我想每个规则都有一个例外。

【讨论】:

两个问题。 1) 当我检查openActivityMap.containsKey(packageName) 时,这意味着它不可为空。有什么方法可以通过不使用? 来即兴创作?? 2) 为什么openActivityMap[packageName]["isAlreadyApplied"] 不可访问?我读了编译器来编译可为空的变量,我们需要输入?.get。但既然,我确定不会是这种情况,我可以摆脱?.get 吗? 老实说,我很失望不能自己说openActivityMap[packageName]["isAlreadyApplied"]——我想。唉,编译器禁止它,我很高兴知道有一个很好的理由(我没有想太多)。我知道你正在试验/学习..我的背景主要是强类型语言,所以我有一定的偏见...... 不确定你的意思,openActivityMap[packageName]!!["lastAppliedAt"]openActivityMap[packageName]?["lastAppliedAt"] 都有 @PythonEnthusiast 要回答你的第一个问题,如果你想获取值,你应该首先获取它并将它存储到一个变量中,然后使用空检查而不是使用containsKey (它将受益于智能演员)。无论如何,将openActivityMap[packageName] 提取到此处的变量将非常有益,因为可以避免多次空检查。请查看我对您的第二个问题的回答,或者如果您想了解更多详细信息。【参考方案3】:

您不能只声明您的Map&lt;String, Any&gt; 以返回Boolean 而不是Any 吗?所以,

val openActivityMap = mutableMapOf<String, MutableMap<String, Boolean>>()

您似乎正在尝试使用第二个 Map 来存储布尔值和整数,这使逻辑复杂化。如果您决定在不键入的情况下使用它,则需要进行类型转换。

【讨论】:

那么最后一次应用是什么? 需要一个新家。【参考方案4】:

下面2条语句有问题

if(openActivityMap[packageName]?.get("isAlreadyApplied"))

if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600)

众所周知,IF 语句的参数需要一个布尔值。这两个语句的类型在编译时是未知的,因为它们是通用类型 Any。因此,

    openActivityMap[packageName]?.get("isAlreadyApplied") 可以为 null 或 Any(非布尔)类型。 openActivityMap[packageName]?.get("lastAppliedAt") 可以为 null 或 Any 类型(此处应使用 Int 进行计算)。

这会引发编译错误,因为编译器不知道要使用的类型。可以做的是将其转换为正确的类型。

解决方案

openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean ?: false

((openActivityMap[packageName]?.get("lastAppliedAt") as Int ?: 0) - currentTime)

如果为空,则给出默认值。

【讨论】:

【参考方案5】:

也许你可以试试这样的东西

        if (openActivityMap.containsKey(packageName)) 
            val packageMap = openActivityMap[packageName]!!
            val applyRequired = (packageMap["lastAppliedAt"] as Long - currentTime) > 3600
            packageMap["isAlreadyApplied"] = packageMap.containsKey("isAlreadyApplied") && !applyRequired
        

顺便说一句。你真的想要lastAppliedAt 在未来吗?否则永远不会是&gt; 3600

【讨论】:

以上是关于Kotlin - 类型不匹配:推断类型是 Any?但布尔值是预期的的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 类型不匹配:推断类型是 View!但 TextView 是预期的

类型不匹配:推断的类型是 String 但在 kotlin 中需要 Charset

kotlin gradle dsl问题:类型不匹配:推断类型是字符串但URI!预计

Android kotlin Object Any 类型不匹配

Android Kotlin - viewBinding 类型不匹配:推断类型为 DrawerLayout 但应为 ConstraintLayout

Kotlin:功能类型不匹配