Java:将集合类型转换为子类型

39
假设类B扩展了类A。我有一个List<A>,我知道它仅包含B的实例。是否有一种方法可以将List<A>转换为List<B>
似乎我的唯一选择是遍历集合,逐个元素进行类型转换,并创建一个新的集合。鉴于类型擦除使得这在运行时完全没有必要,这似乎是对资源的彻底浪费。

2
谢谢大家提供的好答案。在这种情况下,将其转换为列表是最合适的解决方案。在这种情况下,我愿意为了性能而牺牲类型安全。 - Landon Kuhn
4个回答

49

你可以通过未指定类型的List接口进行强制类型转换:

List<A> a = new ArrayList<A>();
List<B> b = (List)a;

5
在这个过程中,会失去所有类型安全性!如果AB的基类,那么列表可以包含A,而处理b的代码则期望它只返回B对象。这正是为什么该情况首先不被允许的原因。 - Joachim Sauer
2
@Joachim,我认为这就是原始问题的重点。与传统转换相比,这种方法唯一的缺点是,如果你错了,bug 直到很久以后才会出现,并且更难找到,而迭代会揭示你的错误。 - Yishai
1
@Joachim - 你正在失去类型安全性;编译器无法保证List<A>确实是一个List<B>,这就是为什么它不允许你在第一次进行转换。如果你自己断言,那没问题,但这并不是静态类型安全的。 - Andrzej Doyle
2
我碰巧知道它只包含B类的实例。只要他只使用生成的列表来检索元素而不是插入元素(这可能会触发对checkedList的运行时检查),他就应该没问题。 - Pavel Minaev
1
Pavel已经掌握了这个问题。问题的条件是他已经确信它只有B的实例。如果没有这个条件,这个问题甚至没有意义。 - M1EK
这样做很糟糕,将非泛型列表强制转换并不是一个好方法。您应该使用List <? extends B>符号以避免可疑和有问题的转换。然后您可以直接分配列表。 - kirhgoff

18

你可以尝试这个:

List<A> a = new ArrayList<A>();
List<B> b = (List<B>) (List<?>) a;

这基于jarnbjo的答案,但不使用原始列表。


1

在我看来:这取决于情况。由于类型擦除,在运行时 List<A> 变成了 List,而 List<B> 变成了 List,因此你的陈述在运行时是错误的,因为 ListList 的子类型(即可分配的类型)。 - pkk
@pkk:这仅适用于您从JVM角度纯粹看待类型系统的情况。泛型几乎完全是一种语言级别的构造,因此JVM对此知之甚少(在这一点上我同意您的观点)。但是,泛型的整个重点在于它们可以与遗留代码结合使用,并且当编译器没有警告时,它们的保证将保持不变。通过原始类型进行强制转换(即执行JVM本来会执行的操作)会破坏类型系统,从而使泛型失去了意义。 - Joachim Sauer
没错。然而,我仍然想知道为什么在语法层面上,以下蕴涵式不成立(即在语言层面上没有实现,并且在我看来应该实现): 只有当A是B的子类型时,Foo<A>才是Foo<B>的子类型。 这将解决上述问题(以及可能与Java中泛型使用相关的其他问题)。 - pkk
1
@pkk:Foo<A> 可能有两种含义:它接受所有的 A 仅返回 A(或者两者都有,甚至在同一个方法中)。对于“它接受所有的 A”(例如作为方法参数),您的建议是正确的。但对于“它仅返回 A”,则不是这样的:Foo<A> 不会返回 B(当然它可能会,但不能保证)。 - Joachim Sauer

1
一种在最小化性能影响的情况下保留某些类型安全性的方法是使用包装器。这个例子是关于集合的,列表的情况非常相似,也许有一天我会写那个。如果有人比我先完成,请让我们分享代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接