Kotlin - ClassCastException 未被捕获

Kotlin - ClassCastException isn't being caught

考虑以下函数

fun issueAssetOrThrow(): ResponseEntity<AssetResponseMessage> {
    val response = getOrThrow(controller.issueAsset(ASSET_REQUEST_MESSAGE))

    return try {
        response as ResponseEntity<AssetResponseMessage>
    } catch (classCastException: ClassCastException) {
        val errorResponse = response as ResponseEntity<ErrorResponseMessage>
        fail(errorResponse.body.error)
    }
}

如果响应成功,getOrThrow(controller.issueAsset(ASSET_REQUEST_MESSAGE)) 将 return ResponseEntity<AssetResponseMessage>

如果响应不成功,它将 return ResponseEntity<ErrorResponseMessage>,除非出现灾难性故障,在这种情况下它会抛出异常。

由于此时响应的通用部分未知,函数应首先 try 将响应转换为 ResponseEntity<AssetResponseMessage>

如果无法执行此转换,它将抛出一个 ClassCastException,然后尝试将响应转换为 ResponseEntity<ErrorResponseMessage>

然后它应该提取错误消息和 fail 这个消息,抛出一个 AssertionError

问题是我实际上得到了这个输出

java.lang.ClassCastException: com.demo.api.messages.ErrorResponseMessage cannot be cast to com.demo.api.messages.AssetResponseMessage

看起来 catch(classCastException: ClassCastException) 被完全忽略了!这是为什么?

感谢 lrcover 的建议 as? 我现在想出了这个解决方案

private fun <TResult> responseOrFail(response: Any): ResponseEntity<TResult> {
    val errorResponse = response as? ResponseEntity<ErrorResponseMessage>

    if (errorResponse != null) {
        fail(errorResponse.body.error)
    }

    return response as ResponseEntity<TResult>
}

然后在我的方法中:

return responseOrFail(response)

现在我得到了想要的结果。

通用类型信息在运行时被删除。话虽这么说,以下内容:

response as ResponseEntity<AssetResponseMessage>

可能已经向您展示了编译器警告(未经检查的转换),实际上与您编写的一样:

response as ResponseEntity<*>

或者让您更加困惑,再次进行另一个未经检查的转换,即使您将 AssetResponseMessage 放在 ResponseEntity:

中,它也可以工作
response as ResponseEntity<ErrorResponseMessage>

这也是为什么我询问特定错误消息的确切抛出位置。它不能被扔进 try 内,也不能被扔进 catch 内。所以唯一缺少的部分是 getOrThrow 本身。

另请注意,您可能更愿意使用 is 并从智能转换中受益,而不是直接转换并希望得到 ClassCastException(针对异常编程是一种反模式)。

这是一个示例:

fun getOrThrow(issueAsset : Any /* I don't mind the actual type for now */) = when (issueAsset) {
    is AssetResponseMessage -> TODO("ResponseEntity<AssetResponseMessage> up to you")
    else -> TODO("error? up to you")
}

或者,您可以使用安全转换运算符 (as?)。安全转换运算符基本上将值设置为 null 以防转换不成功,例如:

 val sample = "test" as? Int
 // now sample has the type Int? and is actually null

注意:转换或安全转换为具有通用类型的 class 将无法确保对象实际包含该通用类型,例如someObj as List<Int> 不会确保您实际上拥有一个整数列表,而只是确保您只有一个 List。请记住:泛型类型信息在运行时被删除。