为什么 Kotlin 在将协变类型参数转换为不变类型参数时会产生 Unchecked Cast 警告?

问题描述 投票:0回答:3

假设我有一个班级

Message
和一个班级
Channel<T : Message>
.

现在,为什么我不能在没有 Unchecked Cast 警告的情况下将

Channel<out Message>
投射到
Channel<Message>

这种转换不应该总是安全的吗,因为

Channel<out Message
只能包含
Message
类型的对象或
Message
类型的子类?

kotlin generics covariance invariance
3个回答
3
投票

不,这是证据。

class Channel<T : Message> {
  fun send(t: T)
  fun receive(): T
}
class Apple : Message()
class AppleChannel : Channel<Apple>() {
  ...
}

val appleChannel = AppleChannel()
val outMessageChannel: Channel<out Message> = appleChannel // MUST WORK
val messageChannel: Channel<Message> = outMessageChannel 
   // you say this should work, but...
messageChannel.send(Orange()) 
   // sending orange on a channel that only knows how to send apples!  
   // Error!

唯一一致的做法是在这种情况下不允许将

Channel<out Message>
转换为
Channel<Message>

如果它被定义为

Channel<out T: Message>
,那么这将是安全的,但是试图定义
send
会导致错误。


2
投票

不,将

out Message
转换为
Message
是不安全的。

Channel<out Message>
基本上意味着
Channel
被限制只能生产
Message
物品,它不能消耗它们,例如因为我们不知道它到底可以消耗什么类型。通过将其转换为
Channel<Message>
,我们允许消耗
Message
。编译器无法在编译时或运行时验证这是正确的,因此它会生成一个未经检查的转换警告。

如果

Channel
类型在你的情况下是为了只生产物品,那么它应该在声明网站上标记为
out T
。然后我们可以简单地使用它
Channel<Message>
.


1
投票

为了完整性,让我添加到以前的答案中。您的问题有 2 个级别。

“未经检查的转换”警告与您尝试转换为/转换的类型无关。这是因为

Channel<X>
Channel<Y>
由于 type erasure 在运行时没有区别。因此,从一个到另一个的转换在运行时永远不会失败,即使它是错误的:它在运行时是未检查(因此是“未检查”)。这可能会导致细微的错误,因为您正在污染堆

这在转换非泛型类型时有所不同,例如

Any
String
,或泛型类型的原始部分,例如
List<*>
MutableList<*>
。如果有问题的实例类型不正确,这些在运行时会正常失败。

这个转换不应该总是安全的吗,因为

Channel<out Message>
只能包含消息类型的对象或
Message
类型的子类?

实际上不,它不安全,正如其他答案所显示的那样。声明中的

out
阻止你向这个频道发送元素(它是
out
-only,你不能放东西
in
)。这可以防止您插入通道不支持的内容。转换会使编译器允许这样做,这在运行时是不正确的。

© www.soinside.com 2019 - 2024. All rights reserved.