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 中非常少见。我猜你的主地图的值不应该是地图本身,而是具有正确类型属性的类的实例(如isAlreadyApplied
和lastAppliedAt
)。您为这些人使用地图有什么特别的原因吗?
【参考方案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<String, Any>
以返回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
在未来吗?否则永远不会是> 3600
【讨论】:
以上是关于Kotlin - 类型不匹配:推断类型是 Any?但布尔值是预期的的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin 类型不匹配:推断类型是 View!但 TextView 是预期的
类型不匹配:推断的类型是 String 但在 kotlin 中需要 Charset
kotlin gradle dsl问题:类型不匹配:推断类型是字符串但URI!预计
Android kotlin Object Any 类型不匹配
Android Kotlin - viewBinding 类型不匹配:推断类型为 DrawerLayout 但应为 ConstraintLayout